// 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