Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 1 | // Copyright 2015 The Weave Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 5 | #include "src/component_manager_impl.h" |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 6 | |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 7 | #include <base/strings/string_number_conversions.h> |
| 8 | #include <base/strings/string_util.h> |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 9 | #include <base/strings/stringprintf.h> |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 10 | |
| 11 | #include "src/commands/schema_constants.h" |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 12 | #include "src/json_error_codes.h" |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 13 | #include "src/string_utils.h" |
| 14 | #include "src/utils.h" |
| 15 | |
| 16 | namespace weave { |
| 17 | |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 18 | namespace { |
| 19 | // Max of 100 state update events should be enough in the queue. |
| 20 | const size_t kMaxStateChangeQueueSize = 100; |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 21 | |
Alex Vakulenko | 1054d3e | 2016-02-23 15:18:15 -0800 | [diff] [blame] | 22 | const char kMinimalRole[] = "minimalRole"; |
| 23 | |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 24 | const EnumToStringMap<UserRole>::Map kMap[] = { |
Alex Vakulenko | 1054d3e | 2016-02-23 15:18:15 -0800 | [diff] [blame] | 25 | {UserRole::kViewer, "viewer"}, |
| 26 | {UserRole::kUser, "user"}, |
| 27 | {UserRole::kOwner, "owner"}, |
| 28 | {UserRole::kManager, "manager"}, |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 29 | }; |
| 30 | } // anonymous namespace |
| 31 | |
| 32 | template <> |
| 33 | LIBWEAVE_EXPORT EnumToStringMap<UserRole>::EnumToStringMap() |
| 34 | : EnumToStringMap(kMap) {} |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 35 | |
Alex Vakulenko | 98d1fee | 2016-02-01 12:25:21 -0800 | [diff] [blame] | 36 | ComponentManagerImpl::ComponentManagerImpl(provider::TaskRunner* task_runner, |
| 37 | base::Clock* clock) |
| 38 | : clock_{clock ? clock : &default_clock_}, |
| 39 | command_queue_{task_runner, clock_} {} |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 40 | |
| 41 | ComponentManagerImpl::~ComponentManagerImpl() {} |
| 42 | |
| 43 | bool ComponentManagerImpl::AddComponent(const std::string& path, |
| 44 | const std::string& name, |
| 45 | const std::vector<std::string>& traits, |
| 46 | ErrorPtr* error) { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 47 | base::DictionaryValue* root = &components_; |
| 48 | if (!path.empty()) { |
| 49 | root = FindComponentGraftNode(path, error); |
| 50 | if (!root) |
| 51 | return false; |
| 52 | } |
| 53 | if (root->GetWithoutPathExpansion(name, nullptr)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 54 | return Error::AddToPrintf(error, FROM_HERE, errors::commands::kInvalidState, |
| 55 | "Component '%s' already exists at path '%s'", |
| 56 | name.c_str(), path.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | // Check to make sure the declared traits are already defined. |
| 60 | for (const std::string& trait : traits) { |
| 61 | if (!FindTraitDefinition(trait)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 62 | return Error::AddToPrintf(error, FROM_HERE, |
| 63 | errors::commands::kInvalidPropValue, |
| 64 | "Trait '%s' is undefined", trait.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 65 | } |
| 66 | } |
| 67 | std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue}; |
| 68 | std::unique_ptr<base::ListValue> traits_list{new base::ListValue}; |
| 69 | traits_list->AppendStrings(traits); |
| 70 | dict->Set("traits", traits_list.release()); |
| 71 | root->SetWithoutPathExpansion(name, dict.release()); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 72 | for (const auto& cb : on_componet_tree_changed_) |
| 73 | cb.Run(); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 74 | return true; |
| 75 | } |
| 76 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 77 | bool ComponentManagerImpl::AddComponentArrayItem( |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 78 | const std::string& path, |
| 79 | const std::string& name, |
| 80 | const std::vector<std::string>& traits, |
| 81 | ErrorPtr* error) { |
| 82 | base::DictionaryValue* root = &components_; |
| 83 | if (!path.empty()) { |
| 84 | root = FindComponentGraftNode(path, error); |
| 85 | if (!root) |
| 86 | return false; |
| 87 | } |
| 88 | base::ListValue* array_value = nullptr; |
| 89 | if (!root->GetListWithoutPathExpansion(name, &array_value)) { |
| 90 | array_value = new base::ListValue; |
| 91 | root->SetWithoutPathExpansion(name, array_value); |
| 92 | } |
| 93 | std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue}; |
| 94 | std::unique_ptr<base::ListValue> traits_list{new base::ListValue}; |
| 95 | traits_list->AppendStrings(traits); |
| 96 | dict->Set("traits", traits_list.release()); |
| 97 | array_value->Append(dict.release()); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 98 | for (const auto& cb : on_componet_tree_changed_) |
| 99 | cb.Run(); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 100 | return true; |
| 101 | } |
| 102 | |
Alex Vakulenko | ce850b5 | 2016-01-04 09:27:50 -0800 | [diff] [blame] | 103 | bool ComponentManagerImpl::RemoveComponent(const std::string& path, |
| 104 | const std::string& name, |
| 105 | ErrorPtr* error) { |
| 106 | base::DictionaryValue* root = &components_; |
| 107 | if (!path.empty()) { |
| 108 | root = FindComponentGraftNode(path, error); |
| 109 | if (!root) |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | if (!root->RemoveWithoutPathExpansion(name, nullptr)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 114 | return Error::AddToPrintf(error, FROM_HERE, errors::commands::kInvalidState, |
| 115 | "Component '%s' does not exist at path '%s'", |
| 116 | name.c_str(), path.c_str()); |
Alex Vakulenko | ce850b5 | 2016-01-04 09:27:50 -0800 | [diff] [blame] | 117 | } |
| 118 | |
| 119 | for (const auto& cb : on_componet_tree_changed_) |
| 120 | cb.Run(); |
| 121 | return true; |
| 122 | } |
| 123 | |
| 124 | bool ComponentManagerImpl::RemoveComponentArrayItem(const std::string& path, |
| 125 | const std::string& name, |
| 126 | size_t index, |
| 127 | ErrorPtr* error) { |
| 128 | base::DictionaryValue* root = &components_; |
| 129 | if (!path.empty()) { |
| 130 | root = FindComponentGraftNode(path, error); |
| 131 | if (!root) |
| 132 | return false; |
| 133 | } |
| 134 | |
| 135 | base::ListValue* array_value = nullptr; |
| 136 | if (!root->GetListWithoutPathExpansion(name, &array_value)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 137 | return Error::AddToPrintf( |
| 138 | error, FROM_HERE, errors::commands::kInvalidState, |
| 139 | "There is no component array named '%s' at path '%s'", name.c_str(), |
| 140 | path.c_str()); |
Alex Vakulenko | ce850b5 | 2016-01-04 09:27:50 -0800 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | if (!array_value->Remove(index, nullptr)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 144 | return Error::AddToPrintf( |
Vitaly Buka | 48a8669 | 2016-01-21 17:15:58 -0800 | [diff] [blame] | 145 | error, FROM_HERE, errors::commands::kInvalidState, |
Alex Vakulenko | ce850b5 | 2016-01-04 09:27:50 -0800 | [diff] [blame] | 146 | "Component array '%s' at path '%s' does not have an element %zu", |
| 147 | name.c_str(), path.c_str(), index); |
Alex Vakulenko | ce850b5 | 2016-01-04 09:27:50 -0800 | [diff] [blame] | 148 | } |
| 149 | |
| 150 | for (const auto& cb : on_componet_tree_changed_) |
| 151 | cb.Run(); |
| 152 | return true; |
| 153 | } |
| 154 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 155 | void ComponentManagerImpl::AddComponentTreeChangedCallback( |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 156 | const base::Closure& callback) { |
| 157 | on_componet_tree_changed_.push_back(callback); |
| 158 | callback.Run(); |
| 159 | } |
| 160 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 161 | bool ComponentManagerImpl::LoadTraits(const base::DictionaryValue& dict, |
| 162 | ErrorPtr* error) { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 163 | bool modified = false; |
| 164 | bool result = true; |
| 165 | // Check if any of the new traits are already defined. If so, make sure the |
| 166 | // definition is exactly the same, or else this is an error. |
| 167 | for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { |
| 168 | if (it.value().GetType() != base::Value::TYPE_DICTIONARY) { |
Vitaly Buka | 48a8669 | 2016-01-21 17:15:58 -0800 | [diff] [blame] | 169 | Error::AddToPrintf(error, FROM_HERE, errors::commands::kTypeMismatch, |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 170 | "Trait '%s' must be an object", it.key().c_str()); |
| 171 | result = false; |
| 172 | break; |
| 173 | } |
| 174 | const base::DictionaryValue* existing_def = nullptr; |
| 175 | if (traits_.GetDictionary(it.key(), &existing_def)) { |
| 176 | if (!existing_def->Equals(&it.value())) { |
Vitaly Buka | 48a8669 | 2016-01-21 17:15:58 -0800 | [diff] [blame] | 177 | Error::AddToPrintf(error, FROM_HERE, errors::commands::kTypeMismatch, |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 178 | "Trait '%s' cannot be redefined", it.key().c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 179 | result = false; |
| 180 | break; |
| 181 | } |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 182 | } else { |
| 183 | traits_.Set(it.key(), it.value().DeepCopy()); |
| 184 | modified = true; |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 185 | } |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 186 | } |
| 187 | |
| 188 | if (modified) { |
| 189 | for (const auto& cb : on_trait_changed_) |
| 190 | cb.Run(); |
| 191 | } |
| 192 | return result; |
| 193 | } |
| 194 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 195 | bool ComponentManagerImpl::LoadTraits(const std::string& json, |
| 196 | ErrorPtr* error) { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 197 | std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error); |
| 198 | if (!dict) |
| 199 | return false; |
| 200 | return LoadTraits(*dict, error); |
| 201 | } |
| 202 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 203 | void ComponentManagerImpl::AddTraitDefChangedCallback( |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 204 | const base::Closure& callback) { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 205 | on_trait_changed_.push_back(callback); |
| 206 | callback.Run(); |
| 207 | } |
| 208 | |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 209 | void ComponentManagerImpl::AddCommand( |
| 210 | std::unique_ptr<CommandInstance> command_instance) { |
| 211 | command_queue_.Add(std::move(command_instance)); |
| 212 | } |
| 213 | |
| 214 | std::unique_ptr<CommandInstance> ComponentManagerImpl::ParseCommandInstance( |
| 215 | const base::DictionaryValue& command, |
| 216 | Command::Origin command_origin, |
| 217 | UserRole role, |
| 218 | std::string* id, |
| 219 | ErrorPtr* error) { |
Alex Vakulenko | b736c98 | 2015-12-05 14:09:27 -0800 | [diff] [blame] | 220 | std::string command_id; |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 221 | auto command_instance = |
| 222 | CommandInstance::FromJson(&command, command_origin, &command_id, error); |
Alex Vakulenko | b736c98 | 2015-12-05 14:09:27 -0800 | [diff] [blame] | 223 | // If we fail to validate the command definition, but there was a command ID |
| 224 | // specified there, return it to the caller when requested. This will be |
| 225 | // used to abort cloud commands. |
| 226 | if (id) |
| 227 | *id = command_id; |
| 228 | |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 229 | if (!command_instance) |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 230 | return nullptr; |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 231 | |
| 232 | UserRole minimal_role; |
| 233 | if (!GetMinimalRole(command_instance->GetName(), &minimal_role, error)) |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 234 | return nullptr; |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 235 | |
| 236 | if (role < minimal_role) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 237 | return Error::AddToPrintf(error, FROM_HERE, "access_denied", |
| 238 | "User role '%s' less than minimal: '%s'", |
| 239 | EnumToString(role).c_str(), |
| 240 | EnumToString(minimal_role).c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 241 | } |
| 242 | |
| 243 | std::string component_path = command_instance->GetComponent(); |
| 244 | if (component_path.empty()) { |
Alex Vakulenko | a3c5e6d | 2015-12-04 17:54:38 -0800 | [diff] [blame] | 245 | // Find the component to which to route this command. Get the trait name |
| 246 | // from the command name and find the first component that has this trait. |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 247 | auto trait_name = |
| 248 | SplitAtFirst(command_instance->GetName(), ".", true).first; |
Alex Vakulenko | a3c5e6d | 2015-12-04 17:54:38 -0800 | [diff] [blame] | 249 | component_path = FindComponentWithTrait(trait_name); |
| 250 | if (component_path.empty()) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 251 | return Error::AddToPrintf( |
Vitaly Buka | 48a8669 | 2016-01-21 17:15:58 -0800 | [diff] [blame] | 252 | error, FROM_HERE, "unrouted_command", |
Alex Vakulenko | a3c5e6d | 2015-12-04 17:54:38 -0800 | [diff] [blame] | 253 | "Unable route command '%s' because there is no component supporting" |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 254 | "trait '%s'", |
| 255 | command_instance->GetName().c_str(), trait_name.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 256 | } |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 257 | command_instance->SetComponent(component_path); |
| 258 | } |
| 259 | |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 260 | const base::DictionaryValue* component = FindComponent(component_path, error); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 261 | if (!component) |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 262 | return nullptr; |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 263 | |
| 264 | // Check that the command's trait is supported by the given component. |
| 265 | auto pair = SplitAtFirst(command_instance->GetName(), ".", true); |
| 266 | |
| 267 | bool trait_supported = false; |
| 268 | const base::ListValue* supported_traits = nullptr; |
| 269 | if (component->GetList("traits", &supported_traits)) { |
| 270 | for (const base::Value* value : *supported_traits) { |
| 271 | std::string trait; |
| 272 | CHECK(value->GetAsString(&trait)); |
| 273 | if (trait == pair.first) { |
| 274 | trait_supported = true; |
| 275 | break; |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | if (!trait_supported) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 281 | return Error::AddToPrintf(error, FROM_HERE, "trait_not_supported", |
| 282 | "Component '%s' doesn't support trait '%s'", |
| 283 | component_path.c_str(), pair.first.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 284 | } |
| 285 | |
Alex Vakulenko | b736c98 | 2015-12-05 14:09:27 -0800 | [diff] [blame] | 286 | if (command_id.empty()) { |
| 287 | command_id = std::to_string(++next_command_id_); |
| 288 | command_instance->SetID(command_id); |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 289 | if (id) |
| 290 | *id = command_id; |
Alex Vakulenko | b736c98 | 2015-12-05 14:09:27 -0800 | [diff] [blame] | 291 | } |
| 292 | |
Alex Vakulenko | d91d625 | 2015-12-05 17:14:39 -0800 | [diff] [blame] | 293 | return command_instance; |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 294 | } |
| 295 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 296 | CommandInstance* ComponentManagerImpl::FindCommand(const std::string& id) { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 297 | return command_queue_.Find(id); |
| 298 | } |
| 299 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 300 | void ComponentManagerImpl::AddCommandAddedCallback( |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 301 | const CommandQueue::CommandCallback& callback) { |
| 302 | command_queue_.AddCommandAddedCallback(callback); |
| 303 | } |
| 304 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 305 | void ComponentManagerImpl::AddCommandRemovedCallback( |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 306 | const CommandQueue::CommandCallback& callback) { |
| 307 | command_queue_.AddCommandRemovedCallback(callback); |
| 308 | } |
| 309 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 310 | void ComponentManagerImpl::AddCommandHandler( |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 311 | const std::string& component_path, |
| 312 | const std::string& command_name, |
| 313 | const Device::CommandHandlerCallback& callback) { |
Alex Vakulenko | 2033433 | 2015-12-08 13:19:49 -0800 | [diff] [blame] | 314 | // If both component_path and command_name are empty, we are adding the |
| 315 | // default handler for all commands. |
| 316 | if (!component_path.empty() || !command_name.empty()) { |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 317 | CHECK(FindCommandDefinition(command_name)) << "Command undefined: " |
| 318 | << command_name; |
Alex Vakulenko | 2033433 | 2015-12-08 13:19:49 -0800 | [diff] [blame] | 319 | } |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 320 | command_queue_.AddCommandHandler(component_path, command_name, callback); |
| 321 | } |
| 322 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 323 | const base::DictionaryValue* ComponentManagerImpl::FindComponent( |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 324 | const std::string& path, |
| 325 | ErrorPtr* error) const { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 326 | return FindComponentAt(&components_, path, error); |
| 327 | } |
| 328 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 329 | const base::DictionaryValue* ComponentManagerImpl::FindTraitDefinition( |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 330 | const std::string& name) const { |
| 331 | const base::DictionaryValue* trait = nullptr; |
| 332 | traits_.GetDictionaryWithoutPathExpansion(name, &trait); |
| 333 | return trait; |
| 334 | } |
| 335 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 336 | const base::DictionaryValue* ComponentManagerImpl::FindCommandDefinition( |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 337 | const std::string& command_name) const { |
| 338 | const base::DictionaryValue* definition = nullptr; |
| 339 | std::vector<std::string> components = Split(command_name, ".", true, false); |
| 340 | // Make sure the |command_name| came in form of trait_name.command_name. |
| 341 | if (components.size() != 2) |
| 342 | return definition; |
| 343 | std::string key = base::StringPrintf("%s.commands.%s", components[0].c_str(), |
| 344 | components[1].c_str()); |
| 345 | traits_.GetDictionary(key, &definition); |
| 346 | return definition; |
| 347 | } |
| 348 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 349 | bool ComponentManagerImpl::GetMinimalRole(const std::string& command_name, |
| 350 | UserRole* minimal_role, |
| 351 | ErrorPtr* error) const { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 352 | const base::DictionaryValue* command = FindCommandDefinition(command_name); |
| 353 | if (!command) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 354 | return Error::AddToPrintf( |
| 355 | error, FROM_HERE, errors::commands::kInvalidCommandName, |
| 356 | "Command definition for '%s' not found", command_name.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 357 | } |
| 358 | std::string value; |
| 359 | // The JSON definition has been pre-validated already in LoadCommands, so |
| 360 | // just using CHECKs here. |
Alex Vakulenko | 1054d3e | 2016-02-23 15:18:15 -0800 | [diff] [blame] | 361 | CHECK(command->GetString(kMinimalRole, &value)); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 362 | CHECK(StringToEnum(value, minimal_role)); |
| 363 | return true; |
| 364 | } |
| 365 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 366 | void ComponentManagerImpl::AddStateChangedCallback( |
| 367 | const base::Closure& callback) { |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 368 | on_state_changed_.push_back(callback); |
| 369 | callback.Run(); // Force to read current state. |
| 370 | } |
| 371 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 372 | bool ComponentManagerImpl::SetStateProperties(const std::string& component_path, |
| 373 | const base::DictionaryValue& dict, |
| 374 | ErrorPtr* error) { |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 375 | base::DictionaryValue* component = |
| 376 | FindMutableComponent(component_path, error); |
| 377 | if (!component) |
| 378 | return false; |
| 379 | |
| 380 | base::DictionaryValue* state = nullptr; |
| 381 | if (!component->GetDictionary("state", &state)) { |
| 382 | state = new base::DictionaryValue; |
| 383 | component->Set("state", state); |
| 384 | } |
| 385 | state->MergeDictionary(&dict); |
| 386 | last_state_change_id_++; |
| 387 | auto& queue = state_change_queues_[component_path]; |
| 388 | if (!queue) |
| 389 | queue.reset(new StateChangeQueue{kMaxStateChangeQueueSize}); |
Vitaly Buka | ece713e | 2015-12-09 10:59:33 -0800 | [diff] [blame] | 390 | base::Time timestamp = clock_->Now(); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 391 | queue->NotifyPropertiesUpdated(timestamp, dict); |
| 392 | for (const auto& cb : on_state_changed_) |
| 393 | cb.Run(); |
| 394 | return true; |
| 395 | } |
| 396 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 397 | bool ComponentManagerImpl::SetStatePropertiesFromJson( |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 398 | const std::string& component_path, |
| 399 | const std::string& json, |
| 400 | ErrorPtr* error) { |
| 401 | std::unique_ptr<const base::DictionaryValue> dict = LoadJsonDict(json, error); |
| 402 | return dict && SetStateProperties(component_path, *dict, error); |
| 403 | } |
| 404 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 405 | const base::Value* ComponentManagerImpl::GetStateProperty( |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 406 | const std::string& component_path, |
| 407 | const std::string& name, |
| 408 | ErrorPtr* error) const { |
| 409 | const base::DictionaryValue* component = FindComponent(component_path, error); |
| 410 | if (!component) |
Alex Vakulenko | 972282c | 2015-12-08 12:30:04 -0800 | [diff] [blame] | 411 | return nullptr; |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 412 | auto pair = SplitAtFirst(name, ".", true); |
| 413 | if (pair.first.empty()) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 414 | return Error::AddToPrintf(error, FROM_HERE, |
| 415 | errors::commands::kPropertyMissing, |
| 416 | "Empty state package in '%s'", name.c_str()); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 417 | } |
| 418 | if (pair.second.empty()) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 419 | return Error::AddToPrintf( |
| 420 | error, FROM_HERE, errors::commands::kPropertyMissing, |
| 421 | "State property name not specified in '%s'", name.c_str()); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 422 | } |
| 423 | std::string key = base::StringPrintf("state.%s", name.c_str()); |
| 424 | const base::Value* value = nullptr; |
| 425 | if (!component->Get(key, &value)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 426 | return Error::AddToPrintf(error, FROM_HERE, |
| 427 | errors::commands::kPropertyMissing, |
| 428 | "State property '%s' not found in component '%s'", |
| 429 | name.c_str(), component_path.c_str()); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 430 | } |
| 431 | return value; |
| 432 | } |
| 433 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 434 | bool ComponentManagerImpl::SetStateProperty(const std::string& component_path, |
| 435 | const std::string& name, |
| 436 | const base::Value& value, |
| 437 | ErrorPtr* error) { |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 438 | base::DictionaryValue dict; |
| 439 | auto pair = SplitAtFirst(name, ".", true); |
| 440 | if (pair.first.empty()) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 441 | return Error::AddToPrintf(error, FROM_HERE, |
| 442 | errors::commands::kPropertyMissing, |
| 443 | "Empty state package in '%s'", name.c_str()); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 444 | } |
| 445 | if (pair.second.empty()) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 446 | return Error::AddToPrintf( |
| 447 | error, FROM_HERE, errors::commands::kPropertyMissing, |
| 448 | "State property name not specified in '%s'", name.c_str()); |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 449 | } |
| 450 | dict.Set(name, value.DeepCopy()); |
| 451 | return SetStateProperties(component_path, dict, error); |
| 452 | } |
| 453 | |
| 454 | ComponentManager::StateSnapshot |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 455 | ComponentManagerImpl::GetAndClearRecordedStateChanges() { |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 456 | StateSnapshot snapshot; |
| 457 | snapshot.update_id = GetLastStateChangeId(); |
| 458 | for (auto& pair : state_change_queues_) { |
| 459 | auto changes = pair.second->GetAndClearRecordedStateChanges(); |
| 460 | auto component = pair.first; |
| 461 | auto conv = [component](weave::StateChange& change) { |
| 462 | return ComponentStateChange{change.timestamp, component, |
| 463 | std::move(change.changed_properties)}; |
| 464 | }; |
| 465 | std::transform(changes.begin(), changes.end(), |
| 466 | std::back_inserter(snapshot.state_changes), conv); |
| 467 | } |
| 468 | |
| 469 | // Sort events by the timestamp. |
| 470 | auto pred = [](const ComponentStateChange& lhs, |
| 471 | const ComponentStateChange& rhs) { |
| 472 | return lhs.timestamp < rhs.timestamp; |
| 473 | }; |
| 474 | std::sort(snapshot.state_changes.begin(), snapshot.state_changes.end(), pred); |
| 475 | state_change_queues_.clear(); |
| 476 | return snapshot; |
| 477 | } |
| 478 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 479 | void ComponentManagerImpl::NotifyStateUpdatedOnServer(UpdateID id) { |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 480 | on_server_state_updated_.Notify(id); |
| 481 | } |
| 482 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 483 | ComponentManager::Token ComponentManagerImpl::AddServerStateUpdatedCallback( |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 484 | const base::Callback<void(UpdateID)>& callback) { |
| 485 | if (state_change_queues_.empty()) |
| 486 | callback.Run(GetLastStateChangeId()); |
| 487 | return Token{on_server_state_updated_.Add(callback).release()}; |
| 488 | } |
| 489 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 490 | std::string ComponentManagerImpl::FindComponentWithTrait( |
Alex Vakulenko | a3c5e6d | 2015-12-04 17:54:38 -0800 | [diff] [blame] | 491 | const std::string& trait) const { |
| 492 | for (base::DictionaryValue::Iterator it(components_); !it.IsAtEnd(); |
| 493 | it.Advance()) { |
| 494 | const base::ListValue* supported_traits = nullptr; |
| 495 | const base::DictionaryValue* component = nullptr; |
| 496 | CHECK(it.value().GetAsDictionary(&component)); |
| 497 | if (component->GetList("traits", &supported_traits)) { |
| 498 | for (const base::Value* value : *supported_traits) { |
| 499 | std::string supported_trait; |
| 500 | CHECK(value->GetAsString(&supported_trait)); |
| 501 | if (trait == supported_trait) |
| 502 | return it.key(); |
| 503 | } |
| 504 | } |
| 505 | } |
| 506 | return std::string{}; |
| 507 | } |
| 508 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 509 | base::DictionaryValue* ComponentManagerImpl::FindComponentGraftNode( |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 510 | const std::string& path, |
| 511 | ErrorPtr* error) { |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 512 | base::DictionaryValue* root = nullptr; |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 513 | base::DictionaryValue* component = FindMutableComponent(path, error); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 514 | if (component && !component->GetDictionary("components", &root)) { |
| 515 | root = new base::DictionaryValue; |
| 516 | component->Set("components", root); |
| 517 | } |
| 518 | return root; |
| 519 | } |
| 520 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 521 | base::DictionaryValue* ComponentManagerImpl::FindMutableComponent( |
Alex Vakulenko | 7b588fc | 2015-12-04 16:03:59 -0800 | [diff] [blame] | 522 | const std::string& path, |
| 523 | ErrorPtr* error) { |
| 524 | return const_cast<base::DictionaryValue*>( |
| 525 | FindComponentAt(&components_, path, error)); |
| 526 | } |
| 527 | |
Alex Vakulenko | ba98115 | 2015-12-05 13:58:22 -0800 | [diff] [blame] | 528 | const base::DictionaryValue* ComponentManagerImpl::FindComponentAt( |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 529 | const base::DictionaryValue* root, |
| 530 | const std::string& path, |
| 531 | ErrorPtr* error) { |
| 532 | auto parts = Split(path, ".", true, false); |
| 533 | std::string root_path; |
| 534 | for (size_t i = 0; i < parts.size(); i++) { |
| 535 | auto element = SplitAtFirst(parts[i], "[", true); |
| 536 | int array_index = -1; |
| 537 | if (element.first.empty()) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 538 | return Error::AddToPrintf( |
| 539 | error, FROM_HERE, errors::commands::kPropertyMissing, |
| 540 | "Empty path element at '%s'", root_path.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 541 | } |
| 542 | if (!element.second.empty()) { |
| 543 | if (element.second.back() != ']') { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 544 | return Error::AddToPrintf( |
| 545 | error, FROM_HERE, errors::commands::kPropertyMissing, |
| 546 | "Invalid array element syntax '%s'", parts[i].c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 547 | } |
| 548 | element.second.pop_back(); |
| 549 | std::string index_str; |
| 550 | base::TrimWhitespaceASCII(element.second, base::TrimPositions::TRIM_ALL, |
| 551 | &index_str); |
| 552 | if (!base::StringToInt(index_str, &array_index) || array_index < 0) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 553 | return Error::AddToPrintf( |
| 554 | error, FROM_HERE, errors::commands::kInvalidPropValue, |
| 555 | "Invalid array index '%s'", element.second.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 556 | } |
| 557 | } |
| 558 | |
| 559 | if (!root_path.empty()) { |
| 560 | // We have processed at least one item in the path before, so now |root| |
| 561 | // points to the actual parent component. We need the root to point to |
| 562 | // the 'components' element containing child sub-components instead. |
| 563 | if (!root->GetDictionary("components", &root)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 564 | return Error::AddToPrintf(error, FROM_HERE, |
| 565 | errors::commands::kPropertyMissing, |
| 566 | "Component '%s' does not exist at '%s'", |
| 567 | element.first.c_str(), root_path.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 568 | } |
| 569 | } |
| 570 | |
| 571 | const base::Value* value = nullptr; |
| 572 | if (!root->GetWithoutPathExpansion(element.first, &value)) { |
Vitaly Buka | 48a8669 | 2016-01-21 17:15:58 -0800 | [diff] [blame] | 573 | Error::AddToPrintf(error, FROM_HERE, errors::commands::kPropertyMissing, |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 574 | "Component '%s' does not exist at '%s'", |
| 575 | element.first.c_str(), root_path.c_str()); |
| 576 | return nullptr; |
| 577 | } |
| 578 | |
| 579 | if (value->GetType() == base::Value::TYPE_LIST && array_index < 0) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 580 | return Error::AddToPrintf(error, FROM_HERE, |
| 581 | errors::commands::kTypeMismatch, |
| 582 | "Element '%s.%s' is an array", |
| 583 | root_path.c_str(), element.first.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 584 | } |
| 585 | if (value->GetType() == base::Value::TYPE_DICTIONARY && array_index >= 0) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 586 | return Error::AddToPrintf(error, FROM_HERE, |
| 587 | errors::commands::kTypeMismatch, |
| 588 | "Element '%s.%s' is not an array", |
| 589 | root_path.c_str(), element.first.c_str()); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 590 | } |
| 591 | |
| 592 | if (value->GetType() == base::Value::TYPE_DICTIONARY) { |
| 593 | CHECK(value->GetAsDictionary(&root)); |
| 594 | } else { |
| 595 | const base::ListValue* component_array = nullptr; |
| 596 | CHECK(value->GetAsList(&component_array)); |
| 597 | const base::Value* component_value = nullptr; |
| 598 | if (!component_array->Get(array_index, &component_value) || |
| 599 | !component_value->GetAsDictionary(&root)) { |
Vitaly Buka | 0dbbf60 | 2016-01-22 11:38:37 -0800 | [diff] [blame] | 600 | return Error::AddToPrintf( |
| 601 | error, FROM_HERE, errors::commands::kPropertyMissing, |
| 602 | "Element '%s.%s' does not contain item #%d", root_path.c_str(), |
| 603 | element.first.c_str(), array_index); |
Alex Vakulenko | 44c1dbe | 2015-12-03 15:35:09 -0800 | [diff] [blame] | 604 | } |
| 605 | } |
| 606 | if (!root_path.empty()) |
| 607 | root_path += '.'; |
| 608 | root_path += parts[i]; |
| 609 | } |
| 610 | return root; |
| 611 | } |
| 612 | |
| 613 | } // namespace weave |