Add support for legacy state/commandDefs to ComponentManager

Added methods to convert legacy state/stateDefs/commandDefs to
the new trait definitions and components and functions to convert
the trait definitions/component tree back to old commandDefs and
state.

Change-Id: Ia03142c53d00bbc4f880389166982167e3c8b1e9
Reviewed-on: https://weave-review.googlesource.com/1787
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/src/component_manager.h b/src/component_manager.h
index 31f9228..fbc18aa 100644
--- a/src/component_manager.h
+++ b/src/component_manager.h
@@ -178,6 +178,17 @@
   virtual std::string FindComponentWithTrait(
       const std::string& trait) const = 0;
 
+  // Support for legacy APIs. Setting command and state definitions.
+  // This translates into modifying a trait definition.
+  virtual bool AddLegacyCommandDefinitions(const base::DictionaryValue& dict,
+                                           ErrorPtr* error) = 0;
+  virtual bool AddLegacyStateDefinitions(const base::DictionaryValue& dict,
+                                         ErrorPtr* error) = 0;
+  // Returns device state for legacy APIs.
+  virtual const base::DictionaryValue& GetLegacyState() const = 0;
+  // Returns command definitions for legacy APIs.
+  virtual const base::DictionaryValue& GetLegacyCommandDefinitions() const = 0;
+
   DISALLOW_COPY_AND_ASSIGN(ComponentManager);
 };
 
diff --git a/src/component_manager_impl.cc b/src/component_manager_impl.cc
index 8cfa95e..1883739 100644
--- a/src/component_manager_impl.cc
+++ b/src/component_manager_impl.cc
@@ -448,6 +448,142 @@
   return std::string{};
 }
 
+bool ComponentManagerImpl::AddLegacyCommandDefinitions(
+    const base::DictionaryValue& dict,
+    ErrorPtr* error) {
+  bool result = true;
+  bool modified = false;
+  for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
+    const base::DictionaryValue* command_dict = nullptr;
+    if (!it.value().GetAsDictionary(&command_dict)) {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kTypeMismatch,
+                         "Package '%s' must be an object", it.key().c_str());
+      result = false;
+      continue;
+    }
+    AddTraitToLegacyComponent(it.key());
+    for (base::DictionaryValue::Iterator it_def(*command_dict);
+         !it_def.IsAtEnd(); it_def.Advance()) {
+      std::string key = base::StringPrintf("%s.commands.%s", it.key().c_str(),
+                                           it_def.key().c_str());
+      if (traits_.GetDictionary(key, nullptr)) {
+        Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                           errors::commands::kInvalidPropValue,
+                           "Redefining command '%s.%s'",
+                           it.key().c_str(), it_def.key().c_str());
+        result = false;
+        continue;
+      }
+      traits_.Set(key, it_def.value().DeepCopy());
+      modified = true;
+    }
+  }
+
+  if (modified) {
+    for (const auto& cb : on_trait_changed_)
+      cb.Run();
+  }
+  return result;
+}
+
+bool ComponentManagerImpl::AddLegacyStateDefinitions(
+    const base::DictionaryValue& dict,
+    ErrorPtr* error) {
+  bool result = true;
+  bool modified = false;
+  for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) {
+    const base::DictionaryValue* state_dict = nullptr;
+    if (!it.value().GetAsDictionary(&state_dict)) {
+      Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                         errors::commands::kTypeMismatch,
+                         "Package '%s' must be an object", it.key().c_str());
+      result = false;
+      continue;
+    }
+    AddTraitToLegacyComponent(it.key());
+    for (base::DictionaryValue::Iterator it_def(*state_dict); !it_def.IsAtEnd();
+         it_def.Advance()) {
+      std::string key = base::StringPrintf("%s.state.%s", it.key().c_str(),
+                                           it_def.key().c_str());
+      if (traits_.GetDictionary(key, nullptr)) {
+        Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                           errors::commands::kInvalidPropValue,
+                           "Redefining state property '%s.%s'",
+                           it.key().c_str(), it_def.key().c_str());
+        result = false;
+        continue;
+      }
+      traits_.Set(key, it_def.value().DeepCopy());
+      modified = true;
+    }
+  }
+
+  if (modified) {
+    for (const auto& cb : on_trait_changed_)
+      cb.Run();
+  }
+  return result;
+}
+
+const base::DictionaryValue& ComponentManagerImpl::GetLegacyState() const {
+  legacy_state_.Clear();
+  // Build state from components.
+  for (base::DictionaryValue::Iterator it(components_); !it.IsAtEnd();
+       it.Advance()) {
+    const base::DictionaryValue* component_dict = nullptr;
+    const base::DictionaryValue* component_state = nullptr;
+    if (it.value().GetAsDictionary(&component_dict) &&
+        component_dict->GetDictionary("state", &component_state)) {
+      legacy_state_.MergeDictionary(component_state);
+    }
+  }
+  return legacy_state_;
+}
+
+const base::DictionaryValue&
+ComponentManagerImpl::GetLegacyCommandDefinitions() const {
+  legacy_command_defs_.Clear();
+  // Build commandDefs from traits.
+  for (base::DictionaryValue::Iterator it(traits_); !it.IsAtEnd();
+       it.Advance()) {
+    const base::DictionaryValue* trait_dict = nullptr;
+    const base::DictionaryValue* trait_commands = nullptr;
+    if (it.value().GetAsDictionary(&trait_dict) &&
+        trait_dict->GetDictionary("commands", &trait_commands)) {
+      base::DictionaryValue dict;
+      dict.Set(it.key(), trait_commands->DeepCopy());
+      legacy_command_defs_.MergeDictionary(&dict);
+    }
+  }
+  return legacy_command_defs_;
+}
+
+void ComponentManagerImpl::AddTraitToLegacyComponent(const std::string& trait) {
+  // First check if we already have a component supporting this trait.
+  if (!FindComponentWithTrait(trait).empty())
+    return;
+
+  // If not, add this trait to the first component available.
+  base::DictionaryValue* component = nullptr;
+  base::DictionaryValue::Iterator it(components_);
+  if (it.IsAtEnd()) {
+    // No components at all. Create a new one with dummy name.
+    // This normally wouldn't happen since libweave creates its own component
+    // at startup.
+    component = new base::DictionaryValue;
+    components_.Set("__weave__", component);
+  } else {
+    CHECK(components_.GetDictionary(it.key(), &component));
+  }
+  base::ListValue* traits = nullptr;
+  if (!component->GetList("traits", &traits)) {
+    traits = new base::ListValue;
+    component->Set("traits", traits);
+  }
+  traits->AppendString(trait);
+}
+
 base::DictionaryValue* ComponentManagerImpl::FindComponentGraftNode(
     const std::string& path, ErrorPtr* error) {
   base::DictionaryValue* root = nullptr;
diff --git a/src/component_manager_impl.h b/src/component_manager_impl.h
index 4f15ecd..9778a93 100644
--- a/src/component_manager_impl.h
+++ b/src/component_manager_impl.h
@@ -147,6 +147,17 @@
   // tree. No sub-components are searched.
   std::string FindComponentWithTrait(const std::string& trait) const override;
 
+  // Support for legacy APIs. Setting command and state definitions.
+  // This translates into modifying a trait definition.
+  bool AddLegacyCommandDefinitions(const base::DictionaryValue& dict,
+                                   ErrorPtr* error) override;
+  bool AddLegacyStateDefinitions(const base::DictionaryValue& dict,
+                                 ErrorPtr* error) override;
+  // Returns device state for legacy APIs.
+  const base::DictionaryValue& GetLegacyState() const override;
+  // Returns command definitions for legacy APIs.
+  const base::DictionaryValue& GetLegacyCommandDefinitions() const override;
+
  private:
   // A helper method to find a JSON element of component at |path| to add new
   // sub-components to.
@@ -155,6 +166,12 @@
   base::DictionaryValue* FindMutableComponent(const std::string& path,
                                               ErrorPtr* error);
 
+  // Legacy API support: Helper function to support state/command definitions.
+  // Adds the given trait to at least one component.
+  // Searches for available components and if none of them already supports this
+  // trait, it adds it to the first available component.
+  void AddTraitToLegacyComponent(const std::string& trait);
+
   // Helper method to find a sub-component given a root node and a relative path
   // from the root to the target component.
   static const base::DictionaryValue* FindComponentAt(
@@ -178,6 +195,10 @@
   // Callback list for state change queue event sinks.
   base::CallbackList<void(UpdateID)> on_server_state_updated_;
 
+  // Legacy API support.
+  mutable base::DictionaryValue legacy_state_;  // Device state.
+  mutable base::DictionaryValue legacy_command_defs_;  // Command definitions.
+
   DISALLOW_COPY_AND_ASSIGN(ComponentManagerImpl);
 };
 
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index dcefebd..8c9b386 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -1126,6 +1126,225 @@
   EXPECT_EQ("", manager.FindComponentWithTrait("trait4"));
 }
 
+TEST(ComponentManager, AddLegacyCommandAndStateDefinitions) {
+  ComponentManagerImpl manager;
+  const char kCommandDefs1[] = R"({
+    "package1": {
+      "command1": {
+        "minimalRole": "user",
+        "parameters": {"height": {"type": "integer"}}
+      },
+      "command2": {
+        "minimalRole": "owner",
+        "parameters": {}
+      }
+    },
+    "package2": {
+      "command1": { "minimalRole": "user" },
+      "command2": { "minimalRole": "owner" }
+    }
+  })";
+  auto json = CreateDictionaryValue(kCommandDefs1);
+  EXPECT_TRUE(manager.AddLegacyCommandDefinitions(*json, nullptr));
+  const char kExpected1[] = R"({
+    "package1": {
+      "commands": {
+        "command1": {
+          "minimalRole": "user",
+          "parameters": {"height": {"type": "integer"}}
+        },
+        "command2": {
+          "minimalRole": "owner",
+          "parameters": {}
+        }
+      }
+    },
+    "package2": {
+      "commands": {
+        "command1": { "minimalRole": "user" },
+        "command2": { "minimalRole": "owner" }
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected1, manager.GetTraits());
+  const char kExpectedComponents1[] = R"({
+    "__weave__": { "traits": ["package1", "package2"] }
+  })";
+  EXPECT_JSON_EQ(kExpectedComponents1, manager.GetComponents());
+
+  const char kCommandDefs2[] = R"({
+    "package2": {
+      "command3": { "minimalRole": "user" }
+    },
+    "package3": {
+      "command1": { "minimalRole": "user" },
+      "command2": { "minimalRole": "owner" }
+    }
+  })";
+  json = CreateDictionaryValue(kCommandDefs2);
+  EXPECT_TRUE(manager.AddLegacyCommandDefinitions(*json, nullptr));
+  const char kExpected2[] = R"({
+    "package1": {
+      "commands": {
+        "command1": {
+          "minimalRole": "user",
+          "parameters": {"height": {"type": "integer"}}
+        },
+        "command2": {
+          "minimalRole": "owner",
+          "parameters": {}
+        }
+      }
+    },
+    "package2": {
+      "commands": {
+        "command1": { "minimalRole": "user" },
+        "command2": { "minimalRole": "owner" },
+        "command3": { "minimalRole": "user" }
+      }
+    },
+    "package3": {
+      "commands": {
+        "command1": { "minimalRole": "user" },
+        "command2": { "minimalRole": "owner" }
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected2, manager.GetTraits());
+  const char kExpectedComponents2[] = R"({
+    "__weave__": { "traits": ["package1", "package2", "package3"] }
+  })";
+  EXPECT_JSON_EQ(kExpectedComponents2, manager.GetComponents());
+
+  // Redefining existing commands.
+  EXPECT_FALSE(manager.AddLegacyCommandDefinitions(*json, nullptr));
+
+  const char kStateDefs1[] = R"({
+    "package1": {
+      "prop1": { "type": "string" },
+      "prop2": { "type": "string" }
+    },
+    "package4": {
+      "prop3": { "type": "string" },
+      "prop4": { "type": "string" }
+    }
+  })";
+  json = CreateDictionaryValue(kStateDefs1);
+  EXPECT_TRUE(manager.AddLegacyStateDefinitions(*json, nullptr));
+  const char kExpectedComponents3[] = R"({
+    "__weave__": { "traits": ["package1", "package2", "package3", "package4"] }
+  })";
+  EXPECT_JSON_EQ(kExpectedComponents3, manager.GetComponents());
+
+  const char kExpected3[] = R"({
+    "package1": {
+      "commands": {
+        "command1": {
+          "minimalRole": "user",
+          "parameters": {"height": {"type": "integer"}}
+        },
+        "command2": {
+          "minimalRole": "owner",
+          "parameters": {}
+        }
+      },
+      "state": {
+        "prop1": { "type": "string" },
+        "prop2": { "type": "string" }
+      }
+    },
+    "package2": {
+      "commands": {
+        "command1": { "minimalRole": "user" },
+        "command2": { "minimalRole": "owner" },
+        "command3": { "minimalRole": "user" }
+      }
+    },
+    "package3": {
+      "commands": {
+        "command1": { "minimalRole": "user" },
+        "command2": { "minimalRole": "owner" }
+      }
+    },
+    "package4": {
+      "state": {
+        "prop3": { "type": "string" },
+        "prop4": { "type": "string" }
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected3, manager.GetTraits());
+  const char kExpectedComponents4[] = R"({
+    "__weave__": { "traits": ["package1", "package2", "package3", "package4"] }
+  })";
+  EXPECT_JSON_EQ(kExpectedComponents4, manager.GetComponents());
+
+  // Redefining existing commands.
+  EXPECT_FALSE(manager.AddLegacyStateDefinitions(*json, nullptr));
+
+  const char kExpected4[] = R"({
+    "package1": {
+      "command1": {
+        "minimalRole": "user",
+        "parameters": {"height": {"type": "integer"}}
+      },
+      "command2": {
+        "minimalRole": "owner",
+        "parameters": {}
+      }
+    },
+    "package2": {
+      "command1": { "minimalRole": "user" },
+      "command2": { "minimalRole": "owner" },
+      "command3": { "minimalRole": "user" }
+    },
+    "package3": {
+      "command1": { "minimalRole": "user" },
+      "command2": { "minimalRole": "owner" }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected4, manager.GetLegacyCommandDefinitions());
+}
+
+TEST(ComponentManager, GetLegacyState) {
+  ComponentManagerImpl manager;
+  const char kTraits[] = R"({
+    "trait1": {
+      "state": {
+        "prop1": { "type": "string" },
+        "prop2": { "type": "string" }
+      }
+    },
+    "trait2": {
+      "state": {
+        "prop3": { "type": "string" },
+        "prop4": { "type": "string" }
+      }
+    }
+  })";
+  auto traits = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager.LoadTraits(*traits, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp1", {"trait1"}, nullptr));
+  ASSERT_TRUE(manager.AddComponent("", "comp2", {"trait2"}, nullptr));
+
+  ASSERT_TRUE(manager.SetStatePropertiesFromJson(
+      "comp1", R"({"trait1": {"prop1": "foo", "prop2": "bar"}})", nullptr));
+  ASSERT_TRUE(manager.SetStatePropertiesFromJson(
+      "comp2", R"({"trait2": {"prop3": "baz", "prop4": "quux"}})", nullptr));
+
+  const char kExpected[] = R"({
+    "trait1": {
+      "prop1": "foo",
+      "prop2": "bar"
+    },
+    "trait2": {
+      "prop3": "baz",
+      "prop4": "quux"
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected, manager.GetLegacyState());
+}
+
 TEST(ComponentManager, TestMockComponentManager) {
   // Check that all the virtual methods are mocked out.
   MockComponentManager mock;
diff --git a/src/mock_component_manager.h b/src/mock_component_manager.h
index 351e902..aa58177 100644
--- a/src/mock_component_manager.h
+++ b/src/mock_component_manager.h
@@ -80,6 +80,13 @@
                   const base::Callback<void(UpdateID)>& callback));
   MOCK_CONST_METHOD1(FindComponentWithTrait,
                      std::string(const std::string& trait));
+  MOCK_METHOD2(AddLegacyCommandDefinitions,
+               bool(const base::DictionaryValue& dict, ErrorPtr* error));
+  MOCK_METHOD2(AddLegacyStateDefinitions,
+               bool(const base::DictionaryValue& dict, ErrorPtr* error));
+  MOCK_CONST_METHOD0(GetLegacyState, const base::DictionaryValue&());
+  MOCK_CONST_METHOD0(GetLegacyCommandDefinitions,
+                     const base::DictionaryValue&());
 
  private:
   StateSnapshot GetAndClearRecordedStateChanges() override {