blob: ecdc4eeca6053ea67dbd8014283aaf6a65f6e901 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "peridot/bin/context_engine/context_writer_impl.h"
#include <memory>
#include <fuchsia/modular/cpp/fidl.h>
#include <lib/entity/cpp/json.h>
#include <lib/fidl/cpp/clone.h>
#include <lib/fit/defer.h>
#include "peridot/bin/context_engine/debug.h"
#include "rapidjson/document.h"
namespace modular {
ContextWriterImpl::ContextWriterImpl(
const fuchsia::modular::ComponentScope& client_info,
ContextRepository* const repository,
fuchsia::modular::EntityResolver* const entity_resolver,
fidl::InterfaceRequest<fuchsia::modular::ContextWriter> request)
: binding_(this, std::move(request)),
repository_(repository),
entity_resolver_(entity_resolver),
weak_factory_(this) {
FXL_DCHECK(repository != nullptr);
// Set up a query to the repository to get our parent id.
if (client_info.is_module_scope()) {
parent_value_selector_.type = fuchsia::modular::ContextValueType::MODULE;
parent_value_selector_.meta = fuchsia::modular::ContextMetadata::New();
parent_value_selector_.meta->story = fuchsia::modular::StoryMetadata::New();
parent_value_selector_.meta->story->id =
client_info.module_scope().story_id;
parent_value_selector_.meta->mod = fuchsia::modular::ModuleMetadata::New();
std::vector<std::string> module_path;
fidl::Clone(client_info.module_scope().module_path, &module_path);
parent_value_selector_.meta->mod->path =
fidl::VectorPtr(std::move(module_path));
}
}
ContextWriterImpl::~ContextWriterImpl() {}
namespace {
fidl::VectorPtr<std::string> Deprecated_GetTypesFromJsonEntity(
const fidl::StringPtr& content) {
// If the content has the @type attribute, take its contents and populate the
// fuchsia::modular::EntityMetadata appropriately, overriding whatever is
// there.
std::vector<std::string> types;
if (!ExtractEntityTypesFromJson(content, &types)) {
FXL_LOG(WARNING) << "Invalid entity metadata in JSON value: " << content;
return {};
}
return fidl::VectorPtr(types);
}
void MaybeFillEntityTypeMetadata(const std::vector<std::string>& types,
fuchsia::modular::ContextValue& value) {
if (value.type != fuchsia::modular::ContextValueType::ENTITY)
return;
if (!value.meta.entity) {
value.meta.entity = fuchsia::modular::EntityMetadata::New();
}
value.meta.entity->type = fidl::VectorPtr(types);
}
bool MaybeFindParentValueId(ContextRepository* repository,
const fuchsia::modular::ContextSelector& selector,
ContextRepository::Id* out) {
// There is technically a race condition here, since on construction, we
// are given a fuchsia::modular::ComponentScope, which contains some metadata
// to find a value in the context engine. It is the responsibility of the
// story_info acquierer to actually create that value, so we query at
// CreateValue()-time because it makes it less likely to hit the race
// condition.
//
// This is only exercised when a Module publishes context explicitly,
// something that we plan to disallow once Links speak in Entities, as then
// Modules that wish to store context can simply write Entities into a new
// link.
auto ids = repository->Select(selector);
if (ids.size() == 1) {
*out = *ids.begin();
return true;
}
return false;
}
} // namespace
void ContextWriterImpl::CreateValue(
fidl::InterfaceRequest<fuchsia::modular::ContextValueWriter> request,
fuchsia::modular::ContextValueType type) {
ContextRepository::Id parent_id;
// We ignore the return value - if it returns false |parent_id| will stay
// default-initialized.
MaybeFindParentValueId(repository_, parent_value_selector_, &parent_id);
auto ptr =
new ContextValueWriterImpl(this, parent_id, type, std::move(request));
AddContextValueWriter(ptr);
}
void ContextWriterImpl::AddContextValueWriter(ContextValueWriterImpl* ptr) {
value_writer_storage_.emplace_back(ptr);
}
void ContextWriterImpl::DestroyContextValueWriter(ContextValueWriterImpl* ptr) {
auto it = std::remove_if(
value_writer_storage_.begin(), value_writer_storage_.end(),
[ptr](const std::unique_ptr<ContextValueWriterImpl>& u_ptr) {
return u_ptr.get() == ptr;
});
value_writer_storage_.erase(it, value_writer_storage_.end());
}
void ContextWriterImpl::WriteEntityTopic(std::string topic,
fidl::StringPtr value) {
auto activity =
repository_->debug()->GetIdleWaiter()->RegisterOngoingActivity();
if (!value) {
// Remove this value.
auto it = topic_value_ids_.find(topic);
if (it != topic_value_ids_.end()) {
repository_->Remove(it->second);
}
return;
}
GetEntityTypesFromEntityReference(
value,
[this, activity, topic, value](const std::vector<std::string>& types) {
fuchsia::modular::ContextValue context_value;
context_value.type = fuchsia::modular::ContextValueType::ENTITY;
context_value.content = value;
context_value.meta.entity = fuchsia::modular::EntityMetadata::New();
context_value.meta.entity->topic = topic;
context_value.meta.entity->type = fidl::VectorPtr(types);
auto it = topic_value_ids_.find(topic);
if (it == topic_value_ids_.end()) {
ContextRepository::Id parent_id;
ContextRepository::Id id;
if (MaybeFindParentValueId(repository_, parent_value_selector_,
&parent_id)) {
id = repository_->Add(parent_id, std::move(context_value));
} else {
id = repository_->Add(std::move(context_value));
}
topic_value_ids_[topic] = id;
} else {
repository_->Update(it->second, std::move(context_value));
}
});
}
void ContextWriterImpl::GetEntityTypesFromEntityReference(
const fidl::StringPtr& reference,
fit::function<void(const std::vector<std::string>&)> done) {
auto activity =
repository_->debug()->GetIdleWaiter()->RegisterOngoingActivity();
// TODO(thatguy): This function could be re-used in multiple places. Move it
// somewhere other places can reach it.
std::unique_ptr<fuchsia::modular::EntityPtr> entity =
std::make_unique<fuchsia::modular::EntityPtr>();
entity_resolver_->ResolveEntity(reference, entity->NewRequest());
auto fallback = fit::defer([done = done.share(), reference] {
// The contents of the fuchsia::modular::Entity value could be a deprecated
// JSON fuchsia::modular::Entity, not an fuchsia::modular::Entity reference.
done(Deprecated_GetTypesFromJsonEntity(reference));
});
(*entity)->GetTypes([this, activity, id = entities_.GetId(&entity),
done = std::move(done), fallback = std::move(fallback)](
const std::vector<std::string>& types) mutable {
done(types);
fallback.cancel();
entities_.erase(id);
});
entities_.emplace(std::move(entity));
}
ContextValueWriterImpl::ContextValueWriterImpl(
ContextWriterImpl* writer, const ContextRepository::Id& parent_id,
fuchsia::modular::ContextValueType type,
fidl::InterfaceRequest<fuchsia::modular::ContextValueWriter> request)
: binding_(this, std::move(request)),
writer_(writer),
parent_id_(parent_id),
type_(type),
value_id_(Future<ContextRepository::Id>::Create(
"ContextValueWriterImpl.value_id_")),
weak_factory_(this) {
binding_.set_error_handler(
[this](zx_status_t status) { writer_->DestroyContextValueWriter(this); });
// When |value_id_| completes, we want to remember it so that we know what
// branch to execute in Set().
value_id_->WeakConstThen(
weak_factory_.GetWeakPtr(),
[this](const ContextRepository::Id& id) { have_value_id_ = true; });
}
ContextValueWriterImpl::~ContextValueWriterImpl() {
// It's possible we haven't actually created a value in |repository_| yet.
// Either we have, and |value_id_| is complete and this callback will be
// called synchronously, or we haven't and |value_id_| will go out of scope
// with *this goes out of scope.
value_id_->WeakConstThen(weak_factory_.GetWeakPtr(),
[this](const ContextRepository::Id& id) {
// Remove the value.
writer_->repository()->Remove(id);
});
}
void ContextValueWriterImpl::CreateChildValue(
fidl::InterfaceRequest<fuchsia::modular::ContextValueWriter> request,
fuchsia::modular::ContextValueType type) {
// We can't create a child value until this value has an ID.
value_id_->WeakConstThen(weak_factory_.GetWeakPtr(),
[this, request = std::move(request), type](
const ContextRepository::Id& value_id) mutable {
auto ptr = new ContextValueWriterImpl(
writer_, value_id, type, std::move(request));
writer_->AddContextValueWriter(ptr);
});
}
void ContextValueWriterImpl::Set(
fidl::StringPtr content, fuchsia::modular::ContextMetadataPtr metadata) {
auto activity = writer_->repository()
->debug()
->GetIdleWaiter()
->RegisterOngoingActivity();
auto done_getting_types =
[weak_this = weak_factory_.GetWeakPtr(), activity, content,
metadata = std::move(metadata)](
const std::vector<std::string>& entity_types) mutable {
if (!weak_this)
return;
if (!weak_this->have_value_id_) {
// We're creating this value for the first time.
fuchsia::modular::ContextValue value;
value.type = weak_this->type_;
value.content = content;
if (metadata) {
fidl::Clone(*metadata, &value.meta);
}
MaybeFillEntityTypeMetadata(entity_types, value);
if (weak_this->parent_id_.empty()) {
weak_this->value_id_->Complete(
weak_this->writer_->repository()->Add(std::move(value)));
} else {
weak_this->value_id_->Complete(
weak_this->writer_->repository()->Add(weak_this->parent_id_,
std::move(value)));
}
} else {
// We can safely capture everything by reference because we know
// |weak_this->value_id_| has been completed, which means this
// callback will be executed immediately.
weak_this->value_id_->ConstThen(
[weak_this, &content, &metadata,
&entity_types](const ContextRepository::Id& value_id) {
if (!weak_this->writer_->repository()->Contains(value_id)) {
FXL_LOG(FATAL)
<< "Trying to update non-existent context value ("
<< value_id << "). New content: " << content
<< ", new metadata: " << metadata;
}
auto value = weak_this->writer_->repository()->Get(value_id);
if (content) {
value->content = content;
}
if (metadata) {
fidl::Clone(*metadata, &value->meta);
}
MaybeFillEntityTypeMetadata(entity_types, *value);
weak_this->writer_->repository()->Update(value_id,
std::move(*value));
});
}
};
if (type_ != fuchsia::modular::ContextValueType::ENTITY || !content) {
// Avoid an extra round-trip to fuchsia::modular::EntityResolver that
// won't get us anything.
done_getting_types({});
} else {
writer_->GetEntityTypesFromEntityReference(content,
std::move(done_getting_types));
}
}
} // namespace modular