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 {