void IcingaApplication::DumpModifiedAttributes(void) { String path = GetModAttrPath(); std::fstream fp; String tempFilename = Utility::CreateTempFile(path + ".XXXXXX", 0644, fp); fp.exceptions(std::ofstream::failbit | std::ofstream::badbit); ConfigObject::Ptr previousObject; ConfigObject::DumpModifiedAttributes(boost::bind(&PersistModAttrHelper, boost::ref(fp), boost::ref(previousObject), _1, _2, _3)); if (previousObject) { ConfigWriter::EmitRaw(fp, "\tobj.version = "); ConfigWriter::EmitValue(fp, 0, previousObject->GetVersion()); ConfigWriter::EmitRaw(fp, "\n}\n"); } fp.close(); #ifdef _WIN32 _unlink(path.CStr()); #endif /* _WIN32 */ if (rename(tempFilename.CStr(), path.CStr()) < 0) { BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("rename") << boost::errinfo_errno(errno) << boost::errinfo_file_name(tempFilename)); } }
static void ConfigObjectModifyAttribute(const String& attr, const Value& value) { ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); ConfigObject::Ptr self = vframe->Self; REQUIRE_NOT_NULL(self); return self->ModifyAttribute(attr, value); }
void ApiListener::PersistMessage(const Dictionary::Ptr& message, const ConfigObject::Ptr& secobj) { double ts = message->Get("ts"); ASSERT(ts != 0); Dictionary::Ptr pmessage = new Dictionary(); pmessage->Set("timestamp", ts); pmessage->Set("message", JsonEncode(message)); if (secobj) { Dictionary::Ptr secname = new Dictionary(); secname->Set("type", secobj->GetReflectionType()->GetName()); secname->Set("name", secobj->GetName()); pmessage->Set("secobj", secname); } boost::mutex::scoped_lock lock(m_LogLock); if (m_LogFile) { NetString::WriteStringToStream(m_LogFile, JsonEncode(pmessage)); m_LogMessageCount++; SetLogMessageTimestamp(ts); if (m_LogMessageCount > 50000) { CloseLogFile(); RotateLogFile(); OpenLogFile(); } } }
static void PersistModAttrHelper(std::fstream& fp, ConfigObject::Ptr& previousObject, const ConfigObject::Ptr& object, const String& attr, const Value& value) { if (object != previousObject) { if (previousObject) { ConfigWriter::EmitRaw(fp, "\tobj.version = "); ConfigWriter::EmitValue(fp, 0, previousObject->GetVersion()); ConfigWriter::EmitRaw(fp, "\n}\n\n"); } ConfigWriter::EmitRaw(fp, "var obj = "); Array::Ptr args1 = new Array(); args1->Add(object->GetReflectionType()->GetName()); args1->Add(object->GetName()); ConfigWriter::EmitFunctionCall(fp, "get_object", args1); ConfigWriter::EmitRaw(fp, "\nif (obj) {\n"); } ConfigWriter::EmitRaw(fp, "\tobj."); Array::Ptr args2 = new Array(); args2->Add(attr); args2->Add(value); ConfigWriter::EmitFunctionCall(fp, "modify_attribute", args2); ConfigWriter::EmitRaw(fp, "\n"); previousObject = object; }
static void ConfigObjectRestoreAttribute(const String& attr) { ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); ConfigObject::Ptr self = vframe->Self; REQUIRE_NOT_NULL(self); return self->RestoreAttribute(attr); }
void CheckerComponent::ObjectHandler(const ConfigObject::Ptr& object) { Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object); if (!checkable) return; Zone::Ptr zone = Zone::GetByName(checkable->GetZoneName()); bool same_zone = (!zone || Zone::GetLocalZone() == zone); { boost::mutex::scoped_lock lock(m_Mutex); if (object->IsActive() && !object->IsPaused() && same_zone) { if (m_PendingCheckables.find(checkable) != m_PendingCheckables.end()) return; m_IdleCheckables.insert(GetCheckableScheduleInfo(checkable)); } else { m_IdleCheckables.erase(checkable); m_PendingCheckables.erase(checkable); } m_CV.notify_all(); } }
BOOST_FOREACH(const Object::Ptr& pobj, DependencyGraph::GetParents((obj))) { ConfigObject::Ptr configObj = dynamic_pointer_cast<ConfigObject>(pobj); if (!configObj) continue; Dictionary::Ptr refInfo = new Dictionary(); refInfo->Set("type", configObj->GetType()->GetName()); refInfo->Set("name", configObj->GetName()); used_by->Add(refInfo); }
bool Zone::CanAccessObject(const ConfigObject::Ptr& object) { Zone::Ptr object_zone; if (object->GetReflectionType() == Zone::TypeInstance) object_zone = static_pointer_cast<Zone>(object); else object_zone = static_pointer_cast<Zone>(object->GetZone()); if (!object_zone) object_zone = Zone::GetLocalZone(); return object_zone->IsChildOf(this); }
bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors) { std::vector<Object::Ptr> parents = DependencyGraph::GetParents(object); Type::Ptr type = object->GetReflectionType(); if (!parents.empty() && !cascade) { if (errors) errors->Add("Object '" + object->GetName() + "' of type '" + type->GetName() + "' cannot be deleted because other objects depend on it. " "Use cascading delete to delete it anyway."); return false; } for (const Object::Ptr& pobj : parents) { ConfigObject::Ptr parentObj = dynamic_pointer_cast<ConfigObject>(pobj); if (!parentObj) continue; DeleteObjectHelper(parentObj, cascade, errors); } ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(type, object->GetName()); try { /* mark this object for cluster delete event */ object->SetExtension("ConfigObjectDeleted", true); /* triggers signal for DB IDO and other interfaces */ object->Deactivate(true); if (item) item->Unregister(); else object->Unregister(); } catch (const std::exception& ex) { if (errors) errors->Add(DiagnosticInformation(ex)); return false; } String path = GetObjectConfigPath(object->GetReflectionType(), object->GetName()); if (Utility::PathExists(path)) { if (unlink(path.CStr()) < 0 && errno != ENOENT) { BOOST_THROW_EXCEPTION(posix_error() << boost::errinfo_api_function("unlink") << boost::errinfo_errno(errno) << boost::errinfo_file_name(path)); } } return true; }
bool ConfigObjectUtility::DeleteObject(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors) { if (object->GetPackage() != "_api") { if (errors) errors->Add("Object cannot be deleted because it was not created using the API."); return false; } return DeleteObjectHelper(object, cascade, errors); }
void ConfigType::UnregisterObject(const ConfigObject::Ptr& object) { String name = object->GetName(); { boost::mutex::scoped_lock lock(m_Mutex); m_ObjectMap.erase(name); m_ObjectVector.erase(std::remove(m_ObjectVector.begin(), m_ObjectVector.end(), object), m_ObjectVector.end()); } }
void ConfigType::UnregisterObject(const ConfigObject::Ptr& object) { String name = object->GetName(); { ObjectLock olock(this); m_ObjectMap.erase(name); m_ObjectVector.erase(std::remove(m_ObjectVector.begin(), m_ObjectVector.end(), object), m_ObjectVector.end()); } }
ValidationError::ValidationError(const ConfigObject::Ptr& object, const std::vector<String>& attributePath, const String& message) : m_Object(object), m_AttributePath(attributePath), m_Message(message) { String path; BOOST_FOREACH(const String& attribute, attributePath) { if (!path.IsEmpty()) path += " -> "; path += "'" + attribute + "'"; } Type::Ptr type = object->GetReflectionType(); m_What = "Validation failed for object '" + object->GetName() + "' of type '" + type->GetName() + "'"; if (!path.IsEmpty()) m_What += "; Attribute " + path; m_What += ": " + message; }
void ConfigObject::RestoreObject(const String& message, int attributeTypes) { Dictionary::Ptr persistentObject = JsonDecode(message); String type = persistentObject->Get("type"); String name = persistentObject->Get("name"); ConfigObject::Ptr object = GetObject(type, name); if (!object) return; #ifdef I2_DEBUG Log(LogDebug, "ConfigObject") << "Restoring object '" << name << "' of type '" << type << "'."; #endif /* I2_DEBUG */ Dictionary::Ptr update = persistentObject->Get("update"); Deserialize(object, update, false, attributeTypes); object->OnStateLoaded(); object->SetStateLoaded(true); }
void ConfigType::RegisterObject(const ConfigObject::Ptr& object) { String name = object->GetName(); { ObjectLock olock(this); ObjectMap::iterator it = m_ObjectMap.find(name); if (it != m_ObjectMap.end()) { if (it->second == object) return; BOOST_THROW_EXCEPTION(ScriptError("An object with type '" + m_Name + "' and name '" + name + "' already exists (" + Convert::ToString(it->second->GetDebugInfo()) + "), new declaration: " + Convert::ToString(object->GetDebugInfo()), object->GetDebugInfo())); } m_ObjectMap[name] = object; m_ObjectVector.push_back(object); } }
BOOST_FOREACH(const ConfigObject::Ptr& obj, objs) { Dictionary::Ptr result1 = new Dictionary(); results->Add(result1); Dictionary::Ptr resultAttrs = new Dictionary(); result1->Set("attrs", resultAttrs); 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); if (!joinedObj) continue; Field field = type->GetFieldInfo(fid); prefix = field.NavigationName; } boost::algorithm::to_lower(prefix); Type::Ptr joinedType = joinedObj->GetReflectionType(); for (int fid = 0; fid < joinedType->GetFieldCount(); fid++) { Field field = joinedType->GetFieldInfo(fid); String aname = prefix + "." + field.Name; if (!attrs.empty() && attrs.find(aname) == attrs.end()) continue; Value val = joinedObj->GetField(fid); /* 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); } }
void ConfigType::RegisterObject(const ConfigObject::Ptr& object) { String name = object->GetName(); { boost::mutex::scoped_lock lock(m_Mutex); auto it = m_ObjectMap.find(name); if (it != m_ObjectMap.end()) { if (it->second == object) return; Type *type = dynamic_cast<Type *>(this); BOOST_THROW_EXCEPTION(ScriptError("An object with type '" + type->GetName() + "' and name '" + name + "' already exists (" + Convert::ToString(it->second->GetDebugInfo()) + "), new declaration: " + Convert::ToString(object->GetDebugInfo()), object->GetDebugInfo())); } m_ObjectMap[name] = object; m_ObjectVector.push_back(object); } }
Dictionary::Ptr ApiActions::RemoveAcknowledgement(const ConfigObject::Ptr& object, const Dictionary::Ptr& params) { Checkable::Ptr checkable = static_pointer_cast<Checkable>(object); if (!checkable) return ApiActions::CreateResult(404, "Cannot remove acknowlegement for non-existent checkable object " + object->GetName() + "."); checkable->ClearAcknowledgement(); checkable->RemoveCommentsByType(CommentAcknowledgement); return ApiActions::CreateResult(200, "Successfully removed acknowledgement for object '" + checkable->GetName() + "'."); }
void ApiListener::SyncRelayMessage(const MessageOrigin::Ptr& origin, const ConfigObject::Ptr& secobj, const Dictionary::Ptr& message, bool log) { double ts = Utility::GetTime(); message->Set("ts", ts); Log(LogNotice, "ApiListener") << "Relaying '" << message->Get("method") << "' message"; if (origin && origin->FromZone) message->Set("originZone", origin->FromZone->GetName()); Zone::Ptr target_zone; if (secobj) { if (secobj->GetReflectionType() == Zone::TypeInstance) target_zone = static_pointer_cast<Zone>(secobj); else target_zone = static_pointer_cast<Zone>(secobj->GetZone()); } if (!target_zone) target_zone = Zone::GetLocalZone(); Endpoint::Ptr master = GetMaster(); bool need_log = !RelayMessageOne(target_zone, origin, message, master); for (const Zone::Ptr& zone : target_zone->GetAllParents()) { if (!RelayMessageOne(zone, origin, message, master)) need_log = true; } if (log && need_log) PersistMessage(message, secobj); }
void DbConnection::UpdateObject(const ConfigObject::Ptr& object) { if (!GetConnected()) return; DbObject::Ptr dbobj = DbObject::GetOrCreateByObject(object); if (dbobj) { bool active = object->IsActive(); if (active) { ActivateObject(dbobj); dbobj->SendConfigUpdate(); dbobj->SendStatusUpdate(); } else DeactivateObject(dbobj); } }
void DbConnection::UpdateObject(const ConfigObject::Ptr& object) { if (!GetConnected()) return; DbObject::Ptr dbobj = DbObject::GetOrCreateByObject(object); if (dbobj) { bool dbActive = GetObjectActive(dbobj); bool active = object->IsActive(); if (active && !dbActive) { ActivateObject(dbobj); dbobj->SendConfigUpdate(); dbobj->SendStatusUpdate(); } else if (!active) { /* Deactivate the deleted object no matter * which state it had in the database. */ DeactivateObject(dbobj); } } }
void DbConnection::UpdateObject(const ConfigObject::Ptr& object) { if (!GetConnected() || Application::IsShuttingDown()) return; DbObject::Ptr dbobj = DbObject::GetOrCreateByObject(object); if (dbobj) { bool dbActive = GetObjectActive(dbobj); bool active = object->IsActive(); if (active) { if (!dbActive) ActivateObject(dbobj); Dictionary::Ptr configFields = dbobj->GetConfigFields(); String configHash = dbobj->CalculateConfigHash(configFields); ASSERT(configHash.GetLength() <= 64); configFields->Set("config_hash", configHash); String cachedHash = GetConfigHash(dbobj); if (cachedHash != configHash) { dbobj->SendConfigUpdateHeavy(configFields); dbobj->SendStatusUpdate(); } else { dbobj->SendConfigUpdateLight(); } } else if (!active) { /* Deactivate the deleted object no matter * which state it had in the database. */ DeactivateObject(dbobj); } } }
DbObject::Ptr DbObject::GetOrCreateByObject(const ConfigObject::Ptr& object) { boost::mutex::scoped_lock lock(GetStaticMutex()); DbObject::Ptr dbobj = object->GetExtension("DbObject"); if (dbobj) return dbobj; DbType::Ptr dbtype = DbType::GetByName(object->GetType()->GetName()); if (!dbtype) return DbObject::Ptr(); Service::Ptr service; String name1, name2; service = dynamic_pointer_cast<Service>(object); if (service) { Host::Ptr host = service->GetHost(); name1 = service->GetHost()->GetName(); name2 = service->GetShortName(); } else { if (object->GetType() == ConfigType::GetByName("CheckCommand") || object->GetType() == ConfigType::GetByName("EventCommand") || object->GetType() == ConfigType::GetByName("NotificationCommand")) { Command::Ptr command = dynamic_pointer_cast<Command>(object); name1 = CompatUtility::GetCommandName(command); } else name1 = object->GetName(); } dbobj = dbtype->GetOrCreateObjectByName(name1, name2); dbobj->SetObject(object); object->SetExtension("DbObject", dbobj); return dbobj; }
String icinga::DiagnosticInformation(const std::exception& ex, bool verbose, StackTrace *stack, ContextTrace *context) { std::ostringstream result; String message = ex.what(); const ValidationError *vex = dynamic_cast<const ValidationError *>(&ex); if (message.IsEmpty()) result << boost::diagnostic_information(ex); else result << "Error: " << message; const ScriptError *dex = dynamic_cast<const ScriptError *>(&ex); if (dex && !dex->GetDebugInfo().Path.IsEmpty()) { result << "\nLocation:\n"; ShowCodeFragment(result, dex->GetDebugInfo()); } if (vex) { DebugInfo di; ConfigObject::Ptr dobj = vex->GetObject(); if (dobj) di = dobj->GetDebugInfo(); Dictionary::Ptr currentHint = vex->GetDebugHint(); Array::Ptr messages; if (currentHint) { BOOST_FOREACH(const String& attr, vex->GetAttributePath()) { Dictionary::Ptr props = currentHint->Get("properties"); if (!props) break; currentHint = props->Get(attr); if (!currentHint) break; messages = currentHint->Get("messages"); } } if (messages && messages->GetLength() > 0) { Array::Ptr message = messages->Get(messages->GetLength() - 1); di.Path = message->Get(1); di.FirstLine = message->Get(2); di.FirstColumn = message->Get(3); di.LastLine = message->Get(4); di.LastColumn = message->Get(5); } if (!di.Path.IsEmpty()) { result << "\nLocation:\n"; ShowCodeFragment(result, di); } } const user_error *uex = dynamic_cast<const user_error *>(&ex); const posix_error *pex = dynamic_cast<const posix_error *>(&ex); if (!uex && !pex && verbose) { const StackTrace *st = boost::get_error_info<StackTraceErrorInfo>(ex); if (st) { result << *st; } else { result << std::endl; if (!stack) stack = GetLastExceptionStack(); if (stack) result << *stack; } if (boost::get_error_info<ContextTraceErrorInfo>(ex) == NULL) { result << std::endl; if (!context) context = GetLastExceptionContext(); if (context) result << *context; } } return result.str(); }
/** * Commits the configuration item by creating a ConfigObject * object. * * @returns The ConfigObject that was created/updated. */ ConfigObject::Ptr ConfigItem::Commit(bool discard) { #ifdef I2_DEBUG Log(LogDebug, "ConfigItem") << "Commit called for ConfigItem Type=" << GetType() << ", Name=" << GetName(); #endif /* I2_DEBUG */ /* Make sure the type is valid. */ Type::Ptr type = Type::GetByName(GetType()); ASSERT(type && ConfigObject::TypeInstance->IsAssignableFrom(type)); if (IsAbstract()) return ConfigObject::Ptr(); ConfigObject::Ptr dobj = static_pointer_cast<ConfigObject>(type->Instantiate()); dobj->SetDebugInfo(m_DebugInfo); dobj->SetZoneName(m_Zone); dobj->SetPackage(m_Package); dobj->SetName(m_Name); DebugHint debugHints; ScriptFrame frame(dobj); if (m_Scope) m_Scope->CopyTo(frame.Locals); try { m_Expression->Evaluate(frame, &debugHints); } catch (const std::exception& ex) { if (m_IgnoreOnError) { Log(LogWarning, "ConfigObject") << "Ignoring config object '" << m_Name << "' of type '" << m_Type << "' due to errors: " << DiagnosticInformation(ex); return ConfigObject::Ptr(); } throw; } if (discard) m_Expression.reset(); String item_name; String short_name = dobj->GetShortName(); if (!short_name.IsEmpty()) { item_name = short_name; dobj->SetName(short_name); } else item_name = m_Name; String name = item_name; NameComposer *nc = dynamic_cast<NameComposer *>(type.get()); if (nc) { name = nc->MakeName(name, dobj); if (name.IsEmpty()) BOOST_THROW_EXCEPTION(std::runtime_error("Could not determine name for object")); } if (name != item_name) dobj->SetShortName(item_name); dobj->SetName(name); try { dobj->OnConfigLoaded(); } catch (const std::exception& ex) { if (m_IgnoreOnError) { Log(LogWarning, "ConfigObject") << "Ignoring config object '" << m_Name << "' of type '" << m_Type << "' due to errors: " << DiagnosticInformation(ex); return ConfigObject::Ptr(); } throw; } Dictionary::Ptr persistentItem = new Dictionary(); persistentItem->Set("type", GetType()); persistentItem->Set("name", GetName()); persistentItem->Set("properties", Serialize(dobj, FAConfig)); Dictionary::Ptr dhint = debugHints.ToDictionary(); persistentItem->Set("debug_hints", dhint); Array::Ptr di = new Array(); di->Add(m_DebugInfo.Path); di->Add(m_DebugInfo.FirstLine); di->Add(m_DebugInfo.FirstColumn); di->Add(m_DebugInfo.LastLine); di->Add(m_DebugInfo.LastColumn); persistentItem->Set("debug_info", di); try { DefaultValidationUtils utils; dobj->Validate(FAConfig, utils); } catch (ValidationError& ex) { if (m_IgnoreOnError) { Log(LogWarning, "ConfigObject") << "Ignoring config object '" << m_Name << "' of type '" << m_Type << "' due to errors: " << DiagnosticInformation(ex); return ConfigObject::Ptr(); } ex.SetDebugHint(dhint); throw; } ConfigCompilerContext::GetInstance()->WriteObject(persistentItem); persistentItem.reset(); dhint.reset(); dobj->Register(); m_Object = dobj; return dobj; }
static bool ObjectNameLessComparer(const ConfigObject::Ptr& a, const ConfigObject::Ptr& b) { return a->GetName() < b->GetName(); }
bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response, const Dictionary::Ptr& params) { if (request.RequestUrl->GetPath().size() < 3 || request.RequestUrl->GetPath().size() > 4) return false; if (request.RequestMethod != "GET") return false; Type::Ptr type = FilterUtility::TypeFromPluralName(request.RequestUrl->GetPath()[2]); if (!type) { HttpUtility::SendJsonError(response, params, 400, "Invalid type specified."); return true; } QueryDescription qd; qd.Types.insert(type->GetName()); qd.Permission = "objects/query/" + type->GetName(); Array::Ptr uattrs, ujoins, umetas; try { uattrs = params->Get("attrs"); } catch (const std::exception&) { HttpUtility::SendJsonError(response, params, 400, "Invalid type for 'attrs' attribute specified. Array type is required."); return true; } try { ujoins = params->Get("joins"); } catch (const std::exception&) { HttpUtility::SendJsonError(response, params, 400, "Invalid type for 'joins' attribute specified. Array type is required."); return true; } try { umetas = params->Get("meta"); } catch (const std::exception&) { HttpUtility::SendJsonError(response, params, 400, "Invalid type for 'meta' attribute specified. Array type is required."); return true; } bool allJoins = HttpUtility::GetLastParameter(params, "all_joins"); params->Set("type", type->GetName()); if (request.RequestUrl->GetPath().size() >= 4) { String attr = type->GetName(); boost::algorithm::to_lower(attr); params->Set(attr, request.RequestUrl->GetPath()[3]); } std::vector<Value> objs; try { objs = FilterUtility::GetFilterTargets(qd, params, user); } catch (const std::exception& ex) { HttpUtility::SendJsonError(response, params, 404, "No objects found.", DiagnosticInformation(ex)); return true; } ArrayData results; results.reserve(objs.size()); std::set<String> joinAttrs; std::set<String> userJoinAttrs; if (ujoins) { ObjectLock olock(ujoins); for (const String& ujoin : ujoins) { userJoinAttrs.insert(ujoin.SubStr(0, ujoin.FindFirstOf("."))); } } for (int fid = 0; fid < type->GetFieldCount(); fid++) { Field field = type->GetFieldInfo(fid); if (!(field.Attributes & FANavigation)) continue; if (!allJoins && userJoinAttrs.find(field.NavigationName) == userJoinAttrs.end()) continue; joinAttrs.insert(field.Name); } for (const ConfigObject::Ptr& obj : objs) { DictionaryData result1{ { "name", obj->GetName() }, { "type", obj->GetReflectionType()->GetName() } }; DictionaryData metaAttrs; if (umetas) { ObjectLock olock(umetas); for (const String& meta : umetas) { if (meta == "used_by") { Array::Ptr used_by = new Array(); metaAttrs.emplace_back("used_by", used_by); for (const Object::Ptr& pobj : DependencyGraph::GetParents((obj))) { ConfigObject::Ptr configObj = dynamic_pointer_cast<ConfigObject>(pobj); if (!configObj) continue; used_by->Add(new Dictionary({ { "type", configObj->GetReflectionType()->GetName() }, { "name", configObj->GetName() } })); } } else if (meta == "location") { metaAttrs.emplace_back("location", obj->GetSourceLocation()); } else { HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for meta: " + meta); return true; } } } result1.emplace_back("meta", new Dictionary(std::move(metaAttrs))); try { result1.emplace_back("attrs", SerializeObjectAttrs(obj, String(), uattrs, false, false)); } catch (const ScriptError& ex) { HttpUtility::SendJsonError(response, params, 400, ex.what()); return true; } DictionaryData joins; for (const String& joinAttr : joinAttrs) { Object::Ptr joinedObj; int fid = type->GetFieldId(joinAttr); if (fid < 0) { HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for join: " + joinAttr); return true; } Field field = type->GetFieldInfo(fid); if (!(field.Attributes & FANavigation)) { HttpUtility::SendJsonError(response, params, 400, "Not a joinable field: " + joinAttr); return true; } joinedObj = obj->NavigateField(fid); if (!joinedObj) continue; String prefix = field.NavigationName; try { joins.emplace_back(prefix, SerializeObjectAttrs(joinedObj, prefix, ujoins, true, allJoins)); } catch (const ScriptError& ex) { HttpUtility::SendJsonError(response, params, 400, ex.what()); return true; } } result1.emplace_back("joins", new Dictionary(std::move(joins))); results.push_back(new Dictionary(std::move(result1))); } Dictionary::Ptr result = new Dictionary({ { "results", new Array(std::move(results)) } }); response.SetStatus(200, "OK"); HttpUtility::SendJsonBody(response, params, result); return true; }