// Copyright 2013 The Chromium 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 "components/variations/variations_associated_data.h"
#include <map>
#include <utility>
#include <vector>
#include "base/memory/singleton.h"
namespace variations {
namespace {
// The internal singleton accessor for the map, used to keep it thread-safe.
class GroupMapAccessor {
public:
typedef std::map<ActiveGroupId, VariationID, ActiveGroupIdCompare>
GroupToIDMap;
// Retrieve the singleton.
static GroupMapAccessor* GetInstance() {
return Singleton<GroupMapAccessor>::get();
}
// Note that this normally only sets the ID for a group the first time, unless
// |force| is set to true, in which case it will always override it.
void AssociateID(IDCollectionKey key,
const ActiveGroupId& group_identifier,
const VariationID id,
const bool force) {
#if !defined(NDEBUG)
DCHECK_EQ(3, ID_COLLECTION_COUNT);
// Ensure that at most one of the trigger/non-trigger web property IDs are
// set.
if (key == GOOGLE_WEB_PROPERTIES || key == GOOGLE_WEB_PROPERTIES_TRIGGER) {
IDCollectionKey other_key = key == GOOGLE_WEB_PROPERTIES ?
GOOGLE_WEB_PROPERTIES_TRIGGER : GOOGLE_WEB_PROPERTIES;
DCHECK_EQ(EMPTY_ID, GetID(other_key, group_identifier));
}
// Validate that all collections with this |group_identifier| have the same
// associated ID.
for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
IDCollectionKey other_key = static_cast<IDCollectionKey>(i);
if (other_key == key)
continue;
VariationID other_id = GetID(other_key, group_identifier);
DCHECK(other_id == EMPTY_ID || other_id == id);
}
#endif
base::AutoLock scoped_lock(lock_);
GroupToIDMap* group_to_id_map = GetGroupToIDMap(key);
if (force ||
group_to_id_map->find(group_identifier) == group_to_id_map->end())
(*group_to_id_map)[group_identifier] = id;
}
VariationID GetID(IDCollectionKey key,
const ActiveGroupId& group_identifier) {
base::AutoLock scoped_lock(lock_);
GroupToIDMap* group_to_id_map = GetGroupToIDMap(key);
GroupToIDMap::const_iterator it = group_to_id_map->find(group_identifier);
if (it == group_to_id_map->end())
return EMPTY_ID;
return it->second;
}
void ClearAllMapsForTesting() {
base::AutoLock scoped_lock(lock_);
for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
GroupToIDMap* map = GetGroupToIDMap(static_cast<IDCollectionKey>(i));
DCHECK(map);
map->clear();
}
}
private:
friend struct DefaultSingletonTraits<GroupMapAccessor>;
// Retrieves the GroupToIDMap for |key|.
GroupToIDMap* GetGroupToIDMap(IDCollectionKey key) {
return &group_to_id_maps_[key];
}
GroupMapAccessor() {
group_to_id_maps_.resize(ID_COLLECTION_COUNT);
}
~GroupMapAccessor() {}
base::Lock lock_;
std::vector<GroupToIDMap> group_to_id_maps_;
DISALLOW_COPY_AND_ASSIGN(GroupMapAccessor);
};
// Singleton helper class that keeps track of the parameters of all variations
// and ensures access to these is thread-safe.
class VariationsParamAssociator {
public:
typedef std::pair<std::string, std::string> VariationKey;
typedef std::map<std::string, std::string> VariationParams;
// Retrieve the singleton.
static VariationsParamAssociator* GetInstance() {
return Singleton<VariationsParamAssociator>::get();
}
bool AssociateVariationParams(const std::string& trial_name,
const std::string& group_name,
const VariationParams& params) {
base::AutoLock scoped_lock(lock_);
if (IsFieldTrialActive(trial_name))
return false;
const VariationKey key(trial_name, group_name);
if (ContainsKey(variation_params_, key))
return false;
variation_params_[key] = params;
return true;
}
bool GetVariationParams(const std::string& trial_name,
VariationParams* params) {
base::AutoLock scoped_lock(lock_);
const std::string group_name =
base::FieldTrialList::FindFullName(trial_name);
const VariationKey key(trial_name, group_name);
if (!ContainsKey(variation_params_, key))
return false;
*params = variation_params_[key];
return true;
}
void ClearAllParamsForTesting() {
base::AutoLock scoped_lock(lock_);
variation_params_.clear();
}
private:
friend struct DefaultSingletonTraits<VariationsParamAssociator>;
VariationsParamAssociator() {}
~VariationsParamAssociator() {}
// Tests whether a field trial is active (i.e. group() has been called on it).
// TODO(asvitkine): Expose this as an API on base::FieldTrial.
bool IsFieldTrialActive(const std::string& trial_name) {
base::FieldTrial::ActiveGroups active_groups;
base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
for (size_t i = 0; i < active_groups.size(); ++i) {
if (active_groups[i].trial_name == trial_name)
return true;
}
return false;
}
base::Lock lock_;
std::map<VariationKey, VariationParams> variation_params_;
DISALLOW_COPY_AND_ASSIGN(VariationsParamAssociator);
};
} // namespace
void AssociateGoogleVariationID(IDCollectionKey key,
const std::string& trial_name,
const std::string& group_name,
VariationID id) {
GroupMapAccessor::GetInstance()->AssociateID(
key, MakeActiveGroupId(trial_name, group_name), id, false);
}
void AssociateGoogleVariationIDForce(IDCollectionKey key,
const std::string& trial_name,
const std::string& group_name,
VariationID id) {
GroupMapAccessor::GetInstance()->AssociateID(
key, MakeActiveGroupId(trial_name, group_name), id, true);
}
VariationID GetGoogleVariationID(IDCollectionKey key,
const std::string& trial_name,
const std::string& group_name) {
return GroupMapAccessor::GetInstance()->GetID(
key, MakeActiveGroupId(trial_name, group_name));
}
bool AssociateVariationParams(
const std::string& trial_name,
const std::string& group_name,
const std::map<std::string, std::string>& params) {
return VariationsParamAssociator::GetInstance()->AssociateVariationParams(
trial_name, group_name, params);
}
bool GetVariationParams(const std::string& trial_name,
std::map<std::string, std::string>* params) {
return VariationsParamAssociator::GetInstance()->GetVariationParams(
trial_name, params);
}
std::string GetVariationParamValue(const std::string& trial_name,
const std::string& param_name) {
std::map<std::string, std::string> params;
if (GetVariationParams(trial_name, ¶ms)) {
std::map<std::string, std::string>::iterator it = params.find(param_name);
if (it != params.end())
return it->second;
}
return std::string();
}
// Functions below are exposed for testing explicitly behind this namespace.
// They simply wrap existing functions in this file.
namespace testing {
void ClearAllVariationIDs() {
GroupMapAccessor::GetInstance()->ClearAllMapsForTesting();
}
void ClearAllVariationParams() {
VariationsParamAssociator::GetInstance()->ClearAllParamsForTesting();
}
} // namespace testing
} // namespace variations