1// Copyright (c) 2012 The Chromium 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
5#ifndef CHROME_BROWSER_EXTENSIONS_API_API_RESOURCE_MANAGER_H_
6#define CHROME_BROWSER_EXTENSIONS_API_API_RESOURCE_MANAGER_H_
7
8#include <map>
9
10#include "base/lazy_instance.h"
11#include "base/memory/linked_ptr.h"
12#include "base/threading/non_thread_safe.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/extensions/api/profile_keyed_api_factory.h"
15#include "chrome/common/extensions/extension.h"
16#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
17#include "content/public/browser/browser_thread.h"
18#include "content/public/browser/notification_observer.h"
19#include "content/public/browser/notification_registrar.h"
20#include "content/public/browser/notification_service.h"
21
22namespace extensions {
23
24// An ApiResourceManager manages the lifetime of a set of resources that
25// ApiFunctions use. Examples are sockets or USB connections.
26//
27// Users of this class should define kThreadId to be the thread that
28// ApiResourceManager to works on. The default is defined in ApiResource.
29// The user must also define a static const char* service_name() that returns
30// the name of the service, and in order for ApiResourceManager to use
31// service_name() friend this class.
32//
33// In the cc file the user must define a GetFactoryInstance() and manage their
34// own instances (typically using LazyInstance or Singleton).
35//
36// E.g.:
37//
38// class Resource {
39//  public:
40//   static const BrowserThread::ID kThreadId = BrowserThread::FILE;
41//  private:
42//   friend class ApiResourceManager<Resource>;
43//   static const char* service_name() {
44//     return "ResourceManager";
45//    }
46// };
47//
48// In the cc file:
49//
50// static base::LazyInstance<ProfileKeyedAPIFactory<
51//     ApiResourceManager<Resource> > >
52//         g_factory = LAZY_INSTANCE_INITIALIZER;
53//
54//
55// template <>
56// ProfileKeyedAPIFactory<ApiResourceManager<Resource> >*
57// ApiResourceManager<Resource>::GetFactoryInstance() {
58//   return &g_factory.Get();
59// }
60template <class T>
61class ApiResourceManager : public ProfileKeyedAPI,
62                           public base::NonThreadSafe,
63                           public content::NotificationObserver {
64 public:
65  explicit ApiResourceManager(Profile* profile)
66      : thread_id_(T::kThreadId),
67        data_(new ApiResourceData(thread_id_)) {
68    registrar_.Add(
69      this,
70      chrome::NOTIFICATION_EXTENSION_UNLOADED,
71      content::NotificationService::AllSources());
72  }
73
74  // For Testing.
75  static ApiResourceManager<T>* CreateApiResourceManagerForTest(
76      Profile* profile,
77      content::BrowserThread::ID thread_id) {
78    ApiResourceManager* manager = new ApiResourceManager<T>(profile);
79    manager->thread_id_ = thread_id;
80    manager->data_.reset(new ApiResourceData(thread_id));
81    return manager;
82  }
83
84  virtual ~ApiResourceManager() {
85    DCHECK(CalledOnValidThread());
86    DCHECK(content::BrowserThread::IsMessageLoopValid(thread_id_)) <<
87        "A unit test is using an ApiResourceManager but didn't provide "
88        "the thread message loop needed for that kind of resource. "
89        "Please ensure that the appropriate message loop is operational.";
90
91    content::BrowserThread::DeleteSoon(thread_id_, FROM_HERE, data_.release());
92  }
93
94  // ProfileKeyedAPI implementation.
95  static ProfileKeyedAPIFactory<ApiResourceManager<T> >* GetFactoryInstance();
96
97  // Convenience method to get the ApiResourceManager for a profile.
98  static ApiResourceManager<T>* Get(Profile* profile) {
99    return ProfileKeyedAPIFactory<ApiResourceManager<T> >::GetForProfile(
100        profile);
101  }
102
103  // Takes ownership.
104  int Add(T* api_resource) {
105    return data_->Add(api_resource);
106  }
107
108  void Remove(const std::string& extension_id, int api_resource_id) {
109    data_->Remove(extension_id, api_resource_id);
110  }
111
112  T* Get(const std::string& extension_id, int api_resource_id) {
113    return data_->Get(extension_id, api_resource_id);
114  }
115
116 protected:
117  // content::NotificationObserver:
118  virtual void Observe(int type,
119                       const content::NotificationSource& source,
120                       const content::NotificationDetails& details) OVERRIDE {
121    switch (type) {
122      case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
123        std::string id =
124            content::Details<extensions::UnloadedExtensionInfo>(details)->
125                extension->id();
126        data_->InitiateCleanup(id);
127        break;
128      }
129    }
130  }
131
132 private:
133  friend class ProfileKeyedAPIFactory<ApiResourceManager<T> >;
134  // ProfileKeyedAPI implementation.
135  static const char* service_name() {
136    return T::service_name();
137  }
138  static const bool kServiceHasOwnInstanceInIncognito = true;
139  static const bool kServiceIsNULLWhileTesting = true;
140
141  // ApiResourceData class handles resource bookkeeping on a thread
142  // where resource lifetime is handled.
143  class ApiResourceData {
144   public:
145    typedef std::map<int, linked_ptr<T> > ApiResourceMap;
146    // Lookup map from extension id's to allocated resource id's.
147    typedef std::map<std::string, base::hash_set<int> > ExtensionToResourceMap;
148
149    explicit ApiResourceData(const content::BrowserThread::ID thread_id)
150        : next_id_(1),
151          thread_id_(thread_id) {
152    }
153
154    int Add(T* api_resource) {
155      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
156      int id = GenerateId();
157      if (id > 0) {
158        linked_ptr<T> resource_ptr(api_resource);
159        api_resource_map_[id] = resource_ptr;
160
161        const std::string& extension_id = api_resource->owner_extension_id();
162        if (extension_resource_map_.find(extension_id) ==
163            extension_resource_map_.end()) {
164          extension_resource_map_[extension_id] = base::hash_set<int>();
165        }
166        extension_resource_map_[extension_id].insert(id);
167
168       return id;
169     }
170     return 0;
171    }
172
173    void Remove(const std::string& extension_id, int api_resource_id) {
174      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
175      if (GetOwnedResource(extension_id, api_resource_id) != NULL) {
176        DCHECK(extension_resource_map_.find(extension_id) !=
177               extension_resource_map_.end());
178        extension_resource_map_[extension_id].erase(api_resource_id);
179        api_resource_map_.erase(api_resource_id);
180      }
181    }
182
183    T* Get(const std::string& extension_id, int api_resource_id) {
184      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
185      return GetOwnedResource(extension_id, api_resource_id);
186    }
187
188    void InitiateCleanup(const std::string& extension_id) {
189      content::BrowserThread::PostTask(thread_id_, FROM_HERE,
190          base::Bind(&ApiResourceData::CleanupResourcesFromExtension,
191                     base::Unretained(this), extension_id));
192    }
193
194   private:
195    T* GetOwnedResource(const std::string& extension_id,
196                        int api_resource_id) {
197      linked_ptr<T> ptr = api_resource_map_[api_resource_id];
198      T* resource = ptr.get();
199      if (resource && extension_id == resource->owner_extension_id())
200        return resource;
201      return NULL;
202    }
203
204    void CleanupResourcesFromExtension(const std::string& extension_id) {
205      DCHECK(content::BrowserThread::CurrentlyOn(thread_id_));
206      if (extension_resource_map_.find(extension_id) !=
207          extension_resource_map_.end()) {
208        base::hash_set<int>& resource_ids =
209            extension_resource_map_[extension_id];
210        for (base::hash_set<int>::iterator it = resource_ids.begin();
211             it != resource_ids.end(); ++it) {
212          api_resource_map_.erase(*it);
213        }
214        extension_resource_map_.erase(extension_id);
215      }
216    }
217
218    int GenerateId() {
219      return next_id_++;
220    }
221
222    int next_id_;
223    const content::BrowserThread::ID thread_id_;
224    ApiResourceMap api_resource_map_;
225    ExtensionToResourceMap extension_resource_map_;
226  };
227
228  content::BrowserThread::ID thread_id_;
229  content::NotificationRegistrar registrar_;
230  scoped_ptr<ApiResourceData> data_;
231};
232
233}  // namespace extensions
234
235#endif  // CHROME_BROWSER_EXTENSIONS_API_API_RESOURCE_MANAGER_H_
236