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 {