Improved Refish subroutes

- getSubroutes() is now a method of the Node class
- getSubroutes() is called only once per node at construction time,
  not at each GET request
- template parameter removed from the Node class

Change-Id: Ie4eb8766717aae566c13c295458fe0dba8ab84c0
Signed-off-by: Borawski.Lukasz <lukasz.borawski@intel.com>
diff --git a/include/webserver_common.hpp b/include/webserver_common.hpp
new file mode 100644
index 0000000..fcfd321
--- /dev/null
+++ b/include/webserver_common.hpp
@@ -0,0 +1,24 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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.
+*/
+#pragma once
+
+#include "token_authorization_middleware.hpp"
+#include "webserver_common.hpp"
+
+using CrowApp = crow::App<crow::PersistentData::Middleware,
+                          crow::TokenAuthorization::Middleware,
+                          crow::SecurityHeadersMiddleware>;
+
diff --git a/redfish-core/include/node.hpp b/redfish-core/include/node.hpp
index a76e5b3..908d1a6 100644
--- a/redfish-core/include/node.hpp
+++ b/redfish-core/include/node.hpp
@@ -17,6 +17,7 @@
 
 #include "privileges.hpp"
 #include "token_authorization_middleware.hpp"
+#include "webserver_common.hpp"
 #include "crow.h"
 
 namespace redfish {
@@ -27,7 +28,7 @@
  */
 class Node {
  public:
-  template <typename CrowApp, typename... Params>
+  template <typename... Params>
   Node(CrowApp& app, EntityPrivileges&& entityPrivileges,
        std::string&& entityUrl, Params... params)
       : entityPrivileges(std::move(entityPrivileges)) {
@@ -42,6 +43,49 @@
 
   virtual ~Node() = default;
 
+  std::string getUrl() const {
+    auto odataId = json.find("@odata.id");
+    if (odataId != json.end() && odataId->is_string()) {
+      return odataId->get<std::string>();
+    }
+    return std::string();
+  }
+
+  /**
+   * @brief Inserts subroute fields into for the node's json in the form:
+   *        "subroute_name" : { "odata.id": "node_url/subroute_name/" }
+   *        Excludes metadata urls starting with "$" and child urls having
+   *        more than one level.
+   *
+   * @return  None
+   */
+  void getSubRoutes(const std::vector<std::unique_ptr<Node>>& allNodes) {
+    std::string url = getUrl();
+
+    for (const auto& node : allNodes) {
+      auto route = node->getUrl();
+
+      if (boost::starts_with(route, url)) {
+        auto subRoute = route.substr(url.size());
+        if (subRoute.empty()) {
+          continue;
+        }
+
+        if (subRoute.at(0) == '/') {
+          subRoute = subRoute.substr(1);
+        }
+
+        if (subRoute.at(subRoute.size() - 1) == '/') {
+          subRoute = subRoute.substr(0, subRoute.size() - 1);
+        }
+
+        if (subRoute[0] != '$' && subRoute.find('/') == std::string::npos) {
+          json[subRoute] = nlohmann::json{{"@odata.id", route}};
+        }
+      }
+    }
+  }
+
  protected:
   // Node is designed to be an abstract class, so doGet is pure virtual
   virtual void doGet(crow::response& res, const crow::request& req,
@@ -65,8 +109,9 @@
     res.end();
   }
 
+  nlohmann::json json;
+
  private:
-  template <typename CrowApp>
   void dispatchRequest(CrowApp& app, const crow::request& req,
                        crow::response& res,
                        const std::vector<std::string>& params) {
@@ -107,24 +152,4 @@
   EntityPrivileges entityPrivileges;
 };
 
-template <typename CrowApp>
-void getRedfishSubRoutes(CrowApp& app, const std::string& url,
-                         nlohmann::json& j) {
-  std::vector<const std::string*> routes = app.get_routes(url);
-
-  for (auto route : routes) {
-    auto redfishSubRoute =
-        route->substr(url.size(), route->size() - url.size() - 1);
-
-    // Exclude: - exact matches,
-    //          - metadata urls starting with "$",
-    //          - urls at the same level
-    if (!redfishSubRoute.empty() && redfishSubRoute[0] != '$' &&
-        redfishSubRoute.find('/') == std::string::npos) {
-      j[redfishSubRoute] = nlohmann::json{{"@odata.id", *route}};
-    }
-  }
-}
-
 }  // namespace redfish
-
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 0e059b8..fc8c080 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -19,6 +19,7 @@
 #include "../lib/redfish_sessions.hpp"
 #include "../lib/roles.hpp"
 #include "../lib/service_root.hpp"
+#include "webserver_common.hpp"
 
 namespace redfish {
 /*
@@ -33,13 +34,16 @@
    *
    * @param[in] app   Crow app on which Redfish will initialize
    */
-  template <typename CrowApp>
   RedfishService(CrowApp& app) {
     nodes.emplace_back(std::make_unique<AccountService>(app));
     nodes.emplace_back(std::make_unique<SessionCollection>(app));
     nodes.emplace_back(std::make_unique<Roles>(app));
     nodes.emplace_back(std::make_unique<RoleCollection>(app));
     nodes.emplace_back(std::make_unique<ServiceRoot>(app));
+
+    for (auto& node : nodes) {
+      node->getSubRoutes(nodes);
+    }
   }
 
  private:
diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp
index 5cbc034..dafa605 100644
--- a/redfish-core/lib/account_service.hpp
+++ b/redfish-core/lib/account_service.hpp
@@ -33,31 +33,29 @@
   AccountService(CrowApp& app)
       : Node(app, EntityPrivileges(std::move(accountServiceOpMap)),
              "/redfish/v1/AccountService/") {
-    nodeJson["@odata.id"] = "/redfish/v1/AccountService";
-    nodeJson["@odata.type"] = "#AccountService.v1_1_0.AccountService";
-    nodeJson["@odata.context"] =
+    Node::json["@odata.id"] = "/redfish/v1/AccountService";
+    Node::json["@odata.type"] = "#AccountService.v1_1_0.AccountService";
+    Node::json["@odata.context"] =
         "/redfish/v1/$metadata#AccountService.AccountService";
-    nodeJson["Id"] = "AccountService";
-    nodeJson["Description"] = "BMC User Accounts";
-    nodeJson["Name"] = "Account Service";
-    nodeJson["Status"]["State"] = "Enabled";
-    nodeJson["Status"]["Health"] = "OK";
-    nodeJson["Status"]["HealthRollup"] = "OK";
-    nodeJson["ServiceEnabled"] = true;
-    nodeJson["MinPasswordLength"] = 1;
-    nodeJson["MaxPasswordLength"] = 20;
-    nodeJson["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts";
-    nodeJson["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
+    Node::json["Id"] = "AccountService";
+    Node::json["Description"] = "BMC User Accounts";
+    Node::json["Name"] = "Account Service";
+    Node::json["Status"]["State"] = "Enabled";
+    Node::json["Status"]["Health"] = "OK";
+    Node::json["Status"]["HealthRollup"] = "OK";
+    Node::json["ServiceEnabled"] = true;
+    Node::json["MinPasswordLength"] = 1;
+    Node::json["MaxPasswordLength"] = 20;
+    Node::json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts";
+    Node::json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
   }
 
  private:
   void doGet(crow::response& res, const crow::request& req,
              const std::vector<std::string>& params) override {
-    res.json_value = nodeJson;
+    res.json_value = Node::json;
     res.end();
   }
-
-  nlohmann::json nodeJson;
 };
 
 }  // namespace redfish
diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp
index 58d5b94..75857a1 100644
--- a/redfish-core/lib/redfish_sessions.hpp
+++ b/redfish-core/lib/redfish_sessions.hpp
@@ -40,14 +40,13 @@
 
 class Sessions : public Node {
  public:
-  template <typename CrowApp>
   Sessions(CrowApp& app)
       : Node(app, EntityPrivileges(std::move(sessionOpMap)),
              "/redfish/v1/SessionService/Sessions/<str>", std::string()) {
-    nodeJson["@odata.type"] = "#Session.v1_0_2.Session";
-    nodeJson["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
-    nodeJson["Name"] = "User Session";
-    nodeJson["Description"] = "Manager User Session";
+    Node::json["@odata.type"] = "#Session.v1_0_2.Session";
+    Node::json["@odata.context"] = "/redfish/v1/$metadata#Session.Session";
+    Node::json["Name"] = "User Session";
+    Node::json["Description"] = "Manager User Session";
   }
 
  private:
@@ -62,12 +61,12 @@
       return;
     }
 
-    nodeJson["Id"] = session->unique_id;
-    nodeJson["UserName"] = session->username;
-    nodeJson["@odata.id"] =
+    Node::json["Id"] = session->unique_id;
+    Node::json["UserName"] = session->username;
+    Node::json["@odata.id"] =
         "/redfish/v1/SessionService/Sessions/" + session->unique_id;
 
-    res.json_value = nodeJson;
+    res.json_value = Node::json;
     res.end();
   }
 
@@ -100,25 +99,22 @@
    * data for created member which should match member's doGet result in 100%
    */
   friend SessionCollection;
-
-  nlohmann::json nodeJson;
 };
 
 class SessionCollection : public Node {
  public:
-  template <typename CrowApp>
   SessionCollection(CrowApp& app)
       : Node(app, EntityPrivileges(std::move(sessionCollectionOpMap)),
              "/redfish/v1/SessionService/Sessions/"),
         memberSession(app) {
-    nodeJson["@odata.type"] = "#SessionCollection.SessionCollection";
-    nodeJson["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
-    nodeJson["@odata.context"] =
+    Node::json["@odata.type"] = "#SessionCollection.SessionCollection";
+    Node::json["@odata.id"] = "/redfish/v1/SessionService/Sessions/";
+    Node::json["@odata.context"] =
         "/redfish/v1/$metadata#SessionCollection.SessionCollection";
-    nodeJson["Name"] = "Session Collection";
-    nodeJson["Description"] = "Session Collection";
-    nodeJson["Members@odata.count"] = 0;
-    nodeJson["Members"] = nlohmann::json::array();
+    Node::json["Name"] = "Session Collection";
+    Node::json["Description"] = "Session Collection";
+    Node::json["Members@odata.count"] = 0;
+    Node::json["Members"] = nlohmann::json::array();
   }
 
  private:
@@ -128,14 +124,14 @@
         crow::PersistentData::session_store->get_unique_ids(
             false, crow::PersistentData::PersistenceType::TIMEOUT);
 
-    nodeJson["Members@odata.count"] = session_ids.size();
-    nodeJson["Members"] = nlohmann::json::array();
+    Node::json["Members@odata.count"] = session_ids.size();
+    Node::json["Members"] = nlohmann::json::array();
     for (const auto& uid : session_ids) {
-      nodeJson["Members"].push_back(
+      Node::json["Members"].push_back(
           {{"@odata.id", "/redfish/v1/SessionService/Sessions/" + *uid}});
     }
 
-    res.json_value = nodeJson;
+    res.json_value = Node::json;
     res.end();
   }
 
@@ -235,7 +231,6 @@
    * member's doGet, as they should return 100% matching data
    */
   Sessions memberSession;
-  nlohmann::json nodeJson;
 };
 
 }  // namespace redfish
diff --git a/redfish-core/lib/roles.hpp b/redfish-core/lib/roles.hpp
index 6a3c0d7..f1a1c61 100644
--- a/redfish-core/lib/roles.hpp
+++ b/redfish-core/lib/roles.hpp
@@ -37,58 +37,52 @@
 
 class Roles : public Node {
  public:
-  template <typename CrowApp>
   Roles(CrowApp& app)
       : Node(app, EntityPrivileges(std::move(roleOpMap)),
              "/redfish/v1/AccountService/Roles/Administrator/") {
-    nodeJson["@odata.id"] = "/redfish/v1/AccountService/Roles/Administrator";
-    nodeJson["@odata.type"] = "#Role.v1_0_2.Role";
-    nodeJson["@odata.context"] = "/redfish/v1/$metadata#Role.Role";
-    nodeJson["Id"] = "Administrator";
-    nodeJson["Name"] = "User Role";
-    nodeJson["Description"] = "Administrator User Role";
-    nodeJson["IsPredefined"] = true;
-    nodeJson["AssignedPrivileges"] = {"Login", "ConfigureManager",
-                                      "ConfigureUsers", "ConfigureSelf",
-                                      "ConfigureComponents"};
-    nodeJson["OemPrivileges"] = nlohmann::json::array();
+    Node::json["@odata.id"] = "/redfish/v1/AccountService/Roles/Administrator";
+    Node::json["@odata.type"] = "#Role.v1_0_2.Role";
+    Node::json["@odata.context"] = "/redfish/v1/$metadata#Role.Role";
+    Node::json["Id"] = "Administrator";
+    Node::json["Name"] = "User Role";
+    Node::json["Description"] = "Administrator User Role";
+    Node::json["IsPredefined"] = true;
+    Node::json["AssignedPrivileges"] = {"Login", "ConfigureManager",
+                                        "ConfigureUsers", "ConfigureSelf",
+                                        "ConfigureComponents"};
+    Node::json["OemPrivileges"] = nlohmann::json::array();
   }
 
  private:
   void doGet(crow::response& res, const crow::request& req,
              const std::vector<std::string>& params) override {
-    res.json_value = nodeJson;
+    res.json_value = Node::json;
     res.end();
   }
-
-  nlohmann::json nodeJson;
 };
 
 class RoleCollection : public Node {
  public:
-  template <typename CrowApp>
   RoleCollection(CrowApp& app)
       : Node(app, EntityPrivileges(std::move(roleCollectionOpMap)),
              "/redfish/v1/AccountService/Roles/") {
-    nodeJson["@odata.id"] = "/redfish/v1/AccountService/Roles";
-    nodeJson["@odata.type"] = "#RoleCollection.RoleCollection";
-    nodeJson["@odata.context"] =
+    Node::json["@odata.id"] = "/redfish/v1/AccountService/Roles";
+    Node::json["@odata.type"] = "#RoleCollection.RoleCollection";
+    Node::json["@odata.context"] =
         "/redfish/v1/$metadata#RoleCollection.RoleCollection";
-    nodeJson["Name"] = "Roles Collection";
-    nodeJson["Description"] = "BMC User Roles";
-    nodeJson["Members@odata.count"] = 1;
-    nodeJson["Members"] = {
-        {{"@odata.id", "/redfish/v1/AccountService/Roles/Administrator"}}};
+    Node::json["Name"] = "Roles Collection";
+    Node::json["Description"] = "BMC User Roles";
+    Node::json["Members@odata.count"] = 1;
+    Node::json["Members"] = {
+        {"@odata.id", "/redfish/v1/AccountService/Roles/Administrator"}};
   }
 
  private:
   void doGet(crow::response& res, const crow::request& req,
              const std::vector<std::string>& params) override {
-    res.json_value = nodeJson;
+    res.json_value = Node::json;
     res.end();
   }
-
-  nlohmann::json nodeJson;
 };
 
 }  // namespace redfish
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index 24ad79d..129d58d 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -29,34 +29,30 @@
 
 class ServiceRoot : public Node {
  public:
-  template <typename CrowApp>
   ServiceRoot(CrowApp& app)
       : Node(app, EntityPrivileges(std::move(serviceRootOpMap)),
              "/redfish/v1/") {
-    nodeJson["@odata.type"] = "#ServiceRoot.v1_1_1.ServiceRoot";
-    nodeJson["@odata.id"] = "/redfish/v1";
-    nodeJson["@odata.context"] =
+    Node::json["@odata.type"] = "#ServiceRoot.v1_1_1.ServiceRoot";
+    Node::json["@odata.id"] = "/redfish/v1/";
+    Node::json["@odata.context"] =
         "/redfish/v1/$metadata#ServiceRoot.ServiceRoot";
-    nodeJson["Id"] = "RootService";
-    nodeJson["Name"] = "Root Service";
-    nodeJson["RedfishVersion"] = "1.1.0";
-    nodeJson["Links"]["Sessions"] = {
-        {"@odata.id", "/redfish/v1/SessionService/Sessions/"}};
-    nodeJson["UUID"] =
+    Node::json["Id"] = "RootService";
+    Node::json["Name"] = "Root Service";
+    Node::json["RedfishVersion"] = "1.1.0";
+    Node::json["Links"]["Sessions"] = {
+        {"@odata.id", "/redfish/v1/SessionService/Sessions"}};
+    Node::json["UUID"] =
         app.template get_middleware<crow::PersistentData::Middleware>()
             .system_uuid;
-    getRedfishSubRoutes(app, "/redfish/v1/", nodeJson);
   }
 
  private:
   void doGet(crow::response& res, const crow::request& req,
              const std::vector<std::string>& params) override {
     res.add_header("Content-Type", "application/json");
-    res.body = nodeJson.dump();
+    res.json_value = Node::json;
     res.end();
   }
-
-  nlohmann::json nodeJson;
 };
 
 }  // namespace redfish
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index f504cc7..53dd127 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -13,6 +13,7 @@
 #include <memory>
 #include <string>
 #include "redfish.hpp"
+#include "webserver_common.hpp"
 #include <crow/app.h>
 #include <boost/asio.hpp>
 #include <systemd/sd-daemon.h>
@@ -43,10 +44,7 @@
   auto io = std::make_shared<boost::asio::io_service>();
   crow::PersistentData::session_store =
       std::make_shared<crow::PersistentData::SessionStore>();
-  crow::App<crow::PersistentData::Middleware,
-            crow::TokenAuthorization::Middleware,
-            crow::SecurityHeadersMiddleware>
-      app(io);
+  CrowApp app(io);
 
 #ifdef CROW_ENABLE_SSL
   std::string ssl_pem_file("server.pem");