Clean up async dbus endpoint

This commit makes the webui more compatible with the dbus backend, and
cleans up the backend to support multiple concurrent transactions, as
well as support for the "enumerate" keyword.

Change-Id: I66144904b5c9b2ce7d1b1147cc80dfd356beb0bc
Signed-off-by: Ed Tanous <ed.tanous@intel.com>
diff --git a/include/openbmc_dbus_rest.hpp b/include/openbmc_dbus_rest.hpp
index 9e5e540..d42c04b 100644
--- a/include/openbmc_dbus_rest.hpp
+++ b/include/openbmc_dbus_rest.hpp
@@ -7,42 +7,35 @@
 #include <dbus/match.hpp>
 #include <dbus/message.hpp>
 #include <dbus_singleton.hpp>
+#include <boost/container/flat_set.hpp>
 
 namespace crow {
 namespace openbmc_mapper {
 
-// TODO(ed) having these as scope globals, and as simple as they are limits the
-// ability to queue multiple async operations at once.  Being able to register
-// "done" callbacks to a queue here that also had a count attached would allow
-// multiple requests to be running at once
-std::atomic<std::size_t> outstanding_async_calls(0);
-nlohmann::json object_paths;
-bool property_matched=false;
 void introspect_objects(crow::response &res, std::string process_name,
-                        std::string path) {
+                        std::string path,
+                        std::shared_ptr<nlohmann::json> transaction) {
   dbus::endpoint introspect_endpoint(
       process_name, path, "org.freedesktop.DBus.Introspectable", "Introspect");
-  outstanding_async_calls++;
   crow::connections::system_bus->async_method_call(
       [&, process_name{std::move(process_name)}, object_path{std::move(path)} ](
           const boost::system::error_code ec,
           const std::string &introspect_xml) {
-        outstanding_async_calls--;
         if (ec) {
-          std::cerr << "Introspect call failed with error: " << ec.message()
-                    << " on process: " << process_name
-                    << " path: " << object_path << "\n";
+          CROW_LOG_ERROR << "Introspect call failed with error: "
+                         << ec.message() << " on process: " << process_name
+                         << " path: " << object_path << "\n";
 
         } else {
-          object_paths.push_back({{"path", object_path}});
+          transaction->push_back({{"path", object_path}});
 
           tinyxml2::XMLDocument doc;
 
           doc.Parse(introspect_xml.c_str());
           tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
           if (pRoot == nullptr) {
-            std::cerr << "XML document failed to parse " << process_name << " "
-                      << path << "\n";
+            CROW_LOG_ERROR << "XML document failed to parse " << process_name
+                           << " " << path << "\n";
 
           } else {
             tinyxml2::XMLElement *node = pRoot->FirstChildElement("node");
@@ -53,26 +46,101 @@
                 newpath += object_path;
               }
               newpath += "/" + child_path;
-              // intropect the subobjects as well
-              introspect_objects(res, process_name, newpath);
+              // introspect the subobjects as well
+              introspect_objects(res, process_name, newpath, transaction);
 
               node = node->NextSiblingElement("node");
             }
           }
         }
         // if we're the last outstanding caller, finish the request
-        if (outstanding_async_calls == 0) {
-          nlohmann::json j{{"status", "ok"},
-                           {"bus_name", process_name},
-                           {"objects", object_paths}};
-
-          res.write(j.dump());
-          object_paths.clear();
+        if (transaction.use_count() == 1) {
+          res.json_value = {{"status", "ok"},
+                            {"bus_name", process_name},
+                            {"objects", *transaction}};
           res.end();
         }
       },
       introspect_endpoint);
 }
+using ManagedObjectType = std::vector<std::pair<
+    dbus::object_path, boost::container::flat_map<
+                           std::string, boost::container::flat_map<
+                                            std::string, dbus::dbus_variant>>>>;
+
+void get_manged_objects_for_enumerate(
+    const std::string &object_name, const std::string &connection_name,
+    crow::response &res, std::shared_ptr<nlohmann::json> transaction) {
+  crow::connections::system_bus->async_method_call(
+      [&res, transaction](const boost::system::error_code ec,
+                          const ManagedObjectType &objects) {
+        if (ec) {
+          CROW_LOG_ERROR << ec;
+        } else {
+          nlohmann::json &data_json = *transaction;
+          for (auto &object_path : objects) {
+            nlohmann::json &object_json = data_json[object_path.first.value];
+            for (const auto &interface : object_path.second) {
+              for (const auto &property : interface.second) {
+                boost::apply_visitor(
+                    [&](auto &&val) { object_json[property.first] = val; },
+                    property.second);
+              }
+            }
+          }
+        }
+
+        if (transaction.use_count() == 1) {
+          res.json_value = {{"message", "200 OK"},
+                            {"status", "ok"},
+                            {"data", std::move(*transaction)}};
+          res.end();
+        }
+      },
+      {connection_name, object_name, "org.freedesktop.DBus.ObjectManager",
+       "GetManagedObjects"});
+}  // namespace openbmc_mapper
+
+using GetSubTreeType = std::vector<
+    std::pair<std::string,
+              std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+
+void handle_enumerate(crow::response &res, const std::string &object_path) {
+  crow::connections::system_bus->async_method_call(
+      [&res, object_path{std::string(object_path)} ](
+          const boost::system::error_code ec,
+          const GetSubTreeType &object_names) {
+        if (ec) {
+          res.code = 500;
+          res.end();
+          return;
+        }
+
+        boost::container::flat_set<std::string> connections;
+
+        for (const auto &object : object_names) {
+          for (const auto &connection : object.second) {
+            connections.insert(connection.first);
+          }
+        }
+
+        if (connections.size() <= 0) {
+          res.code = 404;
+          res.end();
+          return;
+        }
+        auto transaction =
+            std::make_shared<nlohmann::json>(nlohmann::json::object());
+        for (const std::string &connection : connections) {
+          get_manged_objects_for_enumerate(object_path, connection, res,
+                                           transaction);
+        }
+
+      },
+      {"xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
+       "xyz.openbmc_project.ObjectMapper", "GetSubTree"},
+      object_path, (int32_t)0, std::array<std::string, 0>());
+}
 
 template <typename... Middlewares>
 void request_routes(Crow<Middlewares...> &app) {
@@ -86,21 +154,19 @@
         crow::connections::system_bus->async_method_call(
             [&](const boost::system::error_code ec,
                 std::vector<std::string> &names) {
-              std::sort(names.begin(), names.end());
+
               if (ec) {
                 res.code = 500;
               } else {
+                std::sort(names.begin(), names.end());
                 nlohmann::json j{{"status", "ok"}};
                 auto &objects_sub = j["objects"];
                 for (auto &name : names) {
                   objects_sub.push_back({{"name", name}});
                 }
-
-                res.write(j.dump());
+                res.json_value = std::move(j);
               }
-
               res.end();
-
             },
             {"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"});
 
@@ -115,10 +181,9 @@
               if (ec) {
                 res.code = 500;
               } else {
-                nlohmann::json j{{"status", "ok"},
-                                 {"message", "200 OK"},
-                                 {"data", object_paths}};
-                res.body = j.dump();
+                res.json_value = {{"status", "ok"},
+                                  {"message", "200 OK"},
+                                  {"data", std::move(object_paths)}};
               }
               res.end();
             },
@@ -128,25 +193,21 @@
             "", static_cast<int32_t>(99), std::array<std::string, 0>());
       });
 
- CROW_ROUTE(app, "/xyz/<path>")
+  CROW_ROUTE(app, "/xyz/<path>")
       .methods("GET"_method,
                "PUT"_method)([](const crow::request &req, crow::response &res,
                                 const std::string &path) {
-        if (outstanding_async_calls != 0) {
-          res.code = 500;
-          res.body = "request in progress";
-          res.end();
-          return;
-        }
+        std::shared_ptr<nlohmann::json> transaction =
+            std::make_shared<nlohmann::json>(nlohmann::json::object());
         using GetObjectType =
             std::vector<std::pair<std::string, std::vector<std::string>>>;
         std::string object_path;
         std::string dest_property;
         std::string property_set_value;
         size_t attr_position = path.find("/attr/");
-        if (attr_position == path.npos)
+        if (attr_position == path.npos) {
           object_path = "/xyz/" + path;
-        else {
+        } else {
           object_path = "/xyz/" + path.substr(0, attr_position);
           dest_property =
               path.substr((attr_position + strlen("/attr/")), path.length());
@@ -173,11 +234,17 @@
           }
         }
 
+        if (boost::ends_with(object_path, "/enumerate")) {
+          object_path.erase(object_path.end() - 10, object_path.end());
+          handle_enumerate(res, object_path);
+          return;
+        }
+
         crow::connections::system_bus->async_method_call(
-            // object_path intentially captured by value
             [
-                  &, object_path, dest_property{std::move(dest_property)},
-                  property_set_value{std::move(property_set_value)}
+                  &, object_path{std::move(object_path)},
+                  dest_property{std::move(dest_property)},
+                  property_set_value{std::move(property_set_value)}, transaction
             ](const boost::system::error_code ec,
               const GetObjectType &object_names) {
               if (ec) {
@@ -192,30 +259,27 @@
               }
               if (req.method == "GET"_method) {
                 for (auto &interface : object_names[0].second) {
-                  outstanding_async_calls++;
                   crow::connections::system_bus->async_method_call(
                       [&](const boost::system::error_code ec,
                           const std::vector<std::pair<
                               std::string, dbus::dbus_variant>> &properties) {
-                        outstanding_async_calls--;
                         if (ec) {
-                          std::cerr << "Bad dbus request error: " << ec;
+                          CROW_LOG_ERROR << "Bad dbus request error: " << ec;
                         } else {
                           for (auto &property : properties) {
                             boost::apply_visitor(
                                 [&](auto val) {
-                                  object_paths[property.first] = val;
+                                  (*transaction)[property.first] = val;
                                 },
                                 property.second);
                           }
                         }
-                        if (outstanding_async_calls == 0) {
-                          nlohmann::json j{{"status", "ok"},
-                                           {"message", "200 OK"},
-                                           {"data", object_paths}};
-                          res.body = j.dump();
+                        if (transaction.use_count() == 1) {
+                          res.json_value = {{"status", "ok"},
+                                            {"message", "200 OK"},
+                                            {"data", *transaction}};
+
                           res.end();
-                          object_paths.clear();
                         }
                       },
                       {object_names[0].first, object_path,
@@ -224,69 +288,66 @@
                 }
               } else if (req.method == "PUT"_method) {
                 for (auto &interface : object_names[0].second) {
-                  outstanding_async_calls++;
                   crow::connections::system_bus->async_method_call(
                       [
                             &, interface{std::move(interface)},
                             object_names{std::move(object_names)},
                             object_path{std::move(object_path)},
                             dest_property{std::move(dest_property)},
-                            property_set_value{std::move(property_set_value)}
+                            property_set_value{std::move(property_set_value)},
+                            transaction
                       ](const boost::system::error_code ec,
-                        const std::vector<std::pair<
-                            std::string, dbus::dbus_variant>> &properties) {
-                        outstanding_async_calls--;
+                        const boost::container::flat_map<
+                            std::string, dbus::dbus_variant> &properties) {
                         if (ec) {
-                          std::cerr << "Bad dbus request error: " << ec;
+                          CROW_LOG_ERROR << "Bad dbus request error: " << ec;
                         } else {
-                          for (auto &property : properties) {
-                            // search all the properties in the interfaces
-                            if (dest_property.compare(property.first) == 0) {
-                              // find the matched property in the interface
-                              property_matched = true;
-                              dbus::dbus_variant property_value(
-                                  property_set_value);  // create the dbus
-                                                        // variant for dbus call
-                              crow::connections::system_bus->async_method_call(
-                                  [&](const boost::system::error_code ec) {
-                                    // use the method "Set" to set the property
-                                    // value
-                                    if (ec) {
-                                      std::cerr << "Bad dbus request error: "
-                                                << ec;
-                                      res.code = 500;
-                                      res.end();
-                                    } else {
-                                      // find the matched property and send the
-                                      // response
-                                      res.json_value = {{"status", "ok"},
-                                                        {"message", "200 OK"},
-                                                        {"data", NULL}};
-                                      res.end();
-                                      return;
-                                    }
-                                  },
-                                  {object_names[0].first, object_path,
-                                   "org.freedesktop.DBus.Properties", "Set"},
-                                  interface, dest_property, property_value);
-                            }
+                          auto it = properties.find(dest_property);
+                          if (it != properties.end()) {
+                            // find the matched property in the interface
+                            dbus::dbus_variant property_value(
+                                property_set_value);  // create the dbus
+                                                      // variant for dbus call
+                            crow::connections::system_bus->async_method_call(
+                                [&](const boost::system::error_code ec) {
+                                  // use the method "Set" to set the property
+                                  // value
+                                  if (ec) {
+                                    CROW_LOG_ERROR << "Bad dbus request error: "
+                                                   << ec;
+                                  }
+                                  // find the matched property and send the
+                                  // response
+                                  *transaction = {{"status", "ok"},
+                                                  {"message", "200 OK"},
+                                                  {"data", nullptr}};
+
+                                },
+                                {object_names[0].first, object_path,
+                                 "org.freedesktop.DBus.Properties", "Set"},
+                                interface, dest_property, property_value);
                           }
-                          if (outstanding_async_calls == 0) {
-                            // all search has been finished
-                            if (property_matched == false) {
-                              nlohmann::json j{{"status", "error"},
-                                               {"message", "403 Forbidden"},
-                                               {"data",
-                                                {{"message",
-                                                  "The specified property "
-                                                  "cannot be created: " +
-                                                      dest_property}}}};
-                              res.json_value = j;
-                              res.end();
-                              return;
-                            } else
-                              property_matched = false;
+                        }
+                        // if we are the last caller, finish the transaction
+                        if (transaction.use_count() == 1) {
+                          // if nobody filled in the property, all calls either
+                          // errored, or failed
+                          if (transaction == nullptr) {
+                            res.code = 403;
+                            res.json_value = {{"status", "error"},
+                                              {"message", "403 Forbidden"},
+                                              {"data",
+                                               {{"message",
+                                                 "The specified property "
+                                                 "cannot be created: " +
+                                                     dest_property}}}};
+
+                          } else {
+                            res.json_value = *transaction;
                           }
+
+                          res.end();
+                          return;
                         }
                       },
                       {object_names[0].first, object_path,
@@ -304,13 +365,8 @@
   CROW_ROUTE(app, "/bus/system/<str>/")
       .methods("GET"_method)([](const crow::request &req, crow::response &res,
                                 const std::string &connection) {
-        if (outstanding_async_calls != 0) {
-          res.code = 500;
-          res.body = "request in progress";
-          res.end();
-          return;
-        }
-        introspect_objects(res, connection, "/");
+        std::shared_ptr<nlohmann::json> transaction;
+        introspect_objects(res, connection, "/", transaction);
       });
 
   CROW_ROUTE(app, "/bus/system/<str>/<path>")
@@ -333,9 +389,8 @@
           if ((*it).find(".") != std::string::npos) {
             break;
             // THis check is neccesary as the trailing slash gets parsed as
-            // part
-            // of our <path> specifier above, which causes the normal trailing
-            // backslash redirector to fail.
+            // part of our <path> specifier above, which causes the normal
+            // trailing backslash redirector to fail.
           } else if (!it->empty()) {
             object_path += "/" + *it;
           }
@@ -369,7 +424,7 @@
               ](const boost::system::error_code ec,
                 const std::string &introspect_xml) {
                 if (ec) {
-                  std::cerr
+                  CROW_LOG_ERROR
                       << "Introspect call failed with error: " << ec.message()
                       << " on process: " << process_name
                       << " path: " << object_path << "\n";
@@ -380,8 +435,9 @@
                   doc.Parse(introspect_xml.c_str());
                   tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
                   if (pRoot == nullptr) {
-                    std::cerr << "XML document failed to parse " << process_name
-                              << " " << object_path << "\n";
+                    CROW_LOG_ERROR << "XML document failed to parse "
+                                   << process_name << " " << object_path
+                                   << "\n";
                     res.write(nlohmann::json{{"status", "XML parse error"}});
                     res.code = 500;
                   } else {
@@ -395,11 +451,10 @@
 
                       interface = interface->NextSiblingElement("interface");
                     }
-                    nlohmann::json j{{"status", "ok"},
-                                     {"bus_name", process_name},
-                                     {"interfaces", interfaces_array},
-                                     {"object_path", object_path}};
-                    res.write(j.dump());
+                    res.json_value = {{"status", "ok"},
+                                      {"bus_name", process_name},
+                                      {"interfaces", interfaces_array},
+                                      {"object_path", object_path}};
                   }
                 }
                 res.end();
@@ -414,7 +469,7 @@
               ](const boost::system::error_code ec,
                 const std::string &introspect_xml) {
                 if (ec) {
-                  std::cerr
+                  CROW_LOG_ERROR
                       << "Introspect call failed with error: " << ec.message()
                       << " on process: " << process_name
                       << " path: " << object_path << "\n";
@@ -425,8 +480,9 @@
                   doc.Parse(introspect_xml.c_str());
                   tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
                   if (pRoot == nullptr) {
-                    std::cerr << "XML document failed to parse " << process_name
-                              << " " << object_path << "\n";
+                    CROW_LOG_ERROR << "XML document failed to parse "
+                                   << process_name << " " << object_path
+                                   << "\n";
                     res.code = 500;
 
                   } else {
@@ -458,10 +514,9 @@
                           }
                           methods_array.push_back(
                               {{"name", methods->Attribute("name")},
-                               {"uri",
-                                "/bus/system/" + process_name + object_path +
-                                    "/" + interface_name + "/" +
-                                    methods->Attribute("name")},
+                               {"uri", "/bus/system/" + process_name +
+                                           object_path + "/" + interface_name +
+                                           "/" + methods->Attribute("name")},
                                {"args", args_array}});
                           methods = methods->NextSiblingElement("method");
                         }
@@ -476,7 +531,8 @@
                             std::string name = arg->Attribute("name");
                             std::string type = arg->Attribute("type");
                             args_array.push_back({
-                                {"name", name}, {"type", type},
+                                {"name", name},
+                                {"type", type},
                             });
                             arg = arg->NextSiblingElement("arg");
                           }