afdb48fe304c2a5da8613ec183de8505f52d62dc
[odoo/odoo.git] / openerp / modules / registry.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 """ Models registries.
23
24 """
25 from collections import Mapping
26 import logging
27 import os
28 import threading
29
30 import openerp
31 from .. import SUPERUSER_ID
32 from openerp.tools import assertion_report, lazy_property, classproperty, config
33 from openerp.tools.lru import LRU
34
35 _logger = logging.getLogger(__name__)
36
37 class Registry(Mapping):
38     """ Model registry for a particular database.
39
40     The registry is essentially a mapping between model names and model
41     instances. There is one registry instance per database.
42
43     """
44
45     def __init__(self, db_name):
46         super(Registry, self).__init__()
47         self.models = {}    # model name/model instance mapping
48         self._sql_error = {}
49         self._store_function = {}
50         self._pure_function_fields = {}         # {model: [field, ...], ...}
51         self._init = True
52         self._init_parent = {}
53         self._assertion_report = assertion_report.assertion_report()
54         self.fields_by_model = None
55
56         # modules fully loaded (maintained during init phase by `loading` module)
57         self._init_modules = set()
58
59         self.db_name = db_name
60         self._db = openerp.sql_db.db_connect(db_name)
61
62         # special cursor for test mode; None means "normal" mode
63         self.test_cr = None
64
65         # Indicates that the registry is 
66         self.ready = False
67
68         # Inter-process signaling (used only when openerp.multi_process is True):
69         # The `base_registry_signaling` sequence indicates the whole registry
70         # must be reloaded.
71         # The `base_cache_signaling sequence` indicates all caches must be
72         # invalidated (i.e. cleared).
73         self.base_registry_signaling_sequence = None
74         self.base_cache_signaling_sequence = None
75
76         # Flag indicating if at least one model cache has been cleared.
77         # Useful only in a multi-process context.
78         self._any_cache_cleared = False
79
80         cr = self.cursor()
81         has_unaccent = openerp.modules.db.has_unaccent(cr)
82         if openerp.tools.config['unaccent'] and not has_unaccent:
83             _logger.warning("The option --unaccent was given but no unaccent() function was found in database.")
84         self.has_unaccent = openerp.tools.config['unaccent'] and has_unaccent
85         cr.close()
86
87     #
88     # Mapping abstract methods implementation
89     # => mixin provides methods keys, items, values, get, __eq__, and __ne__
90     #
91     def __len__(self):
92         """ Return the size of the registry. """
93         return len(self.models)
94
95     def __iter__(self):
96         """ Return an iterator over all model names. """
97         return iter(self.models)
98
99     def __getitem__(self, model_name):
100         """ Return the model with the given name or raise KeyError if it doesn't exist."""
101         return self.models[model_name]
102
103     def __call__(self, model_name):
104         """ Same as ``self[model_name]``. """
105         return self.models[model_name]
106
107     @lazy_property
108     def pure_function_fields(self):
109         """ Return the list of pure function fields (field objects) """
110         fields = []
111         for mname, fnames in self._pure_function_fields.iteritems():
112             model_fields = self[mname]._fields
113             for fname in fnames:
114                 fields.append(model_fields[fname])
115         return fields
116
117     def do_parent_store(self, cr):
118         for o in self._init_parent:
119             self.get(o)._parent_store_compute(cr)
120         self._init = False
121
122     def obj_list(self):
123         """ Return the list of model names in this registry."""
124         return self.keys()
125
126     def add(self, model_name, model):
127         """ Add or replace a model in the registry."""
128         self.models[model_name] = model
129
130     def load(self, cr, module):
131         """ Load a given module in the registry.
132
133         At the Python level, the modules are already loaded, but not yet on a
134         per-registry level. This method populates a registry with the given
135         modules, i.e. it instanciates all the classes of a the given module
136         and registers them in the registry.
137
138         """
139         from .. import models
140
141         models_to_load = [] # need to preserve loading order
142         lazy_property.reset_all(self)
143
144         # Instantiate registered classes (via the MetaModel automatic discovery
145         # or via explicit constructor call), and add them to the pool.
146         for cls in models.MetaModel.module_to_models.get(module.name, []):
147             # models register themselves in self.models
148             model = cls._build_model(self, cr)
149             if model._name not in models_to_load:
150                 # avoid double-loading models whose declaration is split
151                 models_to_load.append(model._name)
152
153         return [self.models[m] for m in models_to_load]
154
155     def setup_models(self, cr, partial=False):
156         """ Complete the setup of models.
157             This must be called after loading modules and before using the ORM.
158
159             :param partial: ``True`` if all models have not been loaded yet.
160         """
161         # prepare the setup on all models
162         for model in self.models.itervalues():
163             model._prepare_setup_fields(cr, SUPERUSER_ID)
164
165         # do the actual setup from a clean state
166         self._m2m = {}
167         context = {'_setup_fields_partial': partial}
168         for model in self.models.itervalues():
169             model._setup_fields(cr, SUPERUSER_ID, context=context)
170
171     def clear_caches(self):
172         """ Clear the caches
173         This clears the caches associated to methods decorated with
174         ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models.
175         """
176         for model in self.models.itervalues():
177             model.clear_caches()
178         # Special case for ir_ui_menu which does not use openerp.tools.ormcache.
179         ir_ui_menu = self.models.get('ir.ui.menu')
180         if ir_ui_menu is not None:
181             ir_ui_menu.clear_cache()
182
183
184     # Useful only in a multi-process context.
185     def reset_any_cache_cleared(self):
186         self._any_cache_cleared = False
187
188     # Useful only in a multi-process context.
189     def any_cache_cleared(self):
190         return self._any_cache_cleared
191
192     @classmethod
193     def setup_multi_process_signaling(cls, cr):
194         if not openerp.multi_process:
195             return None, None
196
197         # Inter-process signaling:
198         # The `base_registry_signaling` sequence indicates the whole registry
199         # must be reloaded.
200         # The `base_cache_signaling sequence` indicates all caches must be
201         # invalidated (i.e. cleared).
202         cr.execute("""SELECT sequence_name FROM information_schema.sequences WHERE sequence_name='base_registry_signaling'""")
203         if not cr.fetchall():
204             cr.execute("""CREATE SEQUENCE base_registry_signaling INCREMENT BY 1 START WITH 1""")
205             cr.execute("""SELECT nextval('base_registry_signaling')""")
206             cr.execute("""CREATE SEQUENCE base_cache_signaling INCREMENT BY 1 START WITH 1""")
207             cr.execute("""SELECT nextval('base_cache_signaling')""")
208         
209         cr.execute("""
210                     SELECT base_registry_signaling.last_value,
211                            base_cache_signaling.last_value
212                     FROM base_registry_signaling, base_cache_signaling""")
213         r, c = cr.fetchone()
214         _logger.debug("Multiprocess load registry signaling: [Registry: # %s] "\
215                     "[Cache: # %s]",
216                     r, c)
217         return r, c
218
219     def enter_test_mode(self):
220         """ Enter the 'test' mode, where one cursor serves several requests. """
221         assert self.test_cr is None
222         self.test_cr = self._db.test_cursor()
223         RegistryManager.enter_test_mode()
224
225     def leave_test_mode(self):
226         """ Leave the test mode. """
227         assert self.test_cr is not None
228         self.test_cr.force_close()
229         self.test_cr = None
230         RegistryManager.leave_test_mode()
231
232     def cursor(self):
233         """ Return a new cursor for the database. The cursor itself may be used
234             as a context manager to commit/rollback and close automatically.
235         """
236         cr = self.test_cr
237         if cr is not None:
238             # While in test mode, we use one special cursor across requests. The
239             # test cursor uses a reentrant lock to serialize accesses. The lock
240             # is granted here by cursor(), and automatically released by the
241             # cursor itself in its method close().
242             cr.acquire()
243             return cr
244         return self._db.cursor()
245
246 class DummyRLock(object):
247     """ Dummy reentrant lock, to be used while running rpc and js tests """
248     def acquire(self):
249         pass
250     def release(self):
251         pass
252     def __enter__(self):
253         self.acquire()
254     def __exit__(self, type, value, traceback):
255         self.release()
256
257 class RegistryManager(object):
258     """ Model registries manager.
259
260         The manager is responsible for creation and deletion of model
261         registries (essentially database connection/model registry pairs).
262
263     """
264     _registries = None
265     _lock = threading.RLock()
266     _saved_lock = None
267
268     @classproperty
269     def registries(cls):
270         if cls._registries is None:
271             size = config.get('registry_lru_size', None)
272             if not size:
273                 # Size the LRU depending of the memory limits
274                 if os.name != 'posix':
275                     # cannot specify the memory limit soft on windows...
276                     size = 42
277                 else:
278                     # On average, a clean registry take 25MB of memory + cache
279                     avgsz = 30 * 1024 * 1024
280                     size = int(config['limit_memory_soft'] / avgsz)
281
282             cls._registries = LRU(size)
283         return cls._registries
284
285     @classmethod
286     def lock(cls):
287         """ Return the current registry lock. """
288         return cls._lock
289
290     @classmethod
291     def enter_test_mode(cls):
292         """ Enter the 'test' mode, where the registry is no longer locked. """
293         assert cls._saved_lock is None
294         cls._lock, cls._saved_lock = DummyRLock(), cls._lock
295
296     @classmethod
297     def leave_test_mode(cls):
298         """ Leave the 'test' mode. """
299         assert cls._saved_lock is not None
300         cls._lock, cls._saved_lock = cls._saved_lock, None
301
302     @classmethod
303     def get(cls, db_name, force_demo=False, status=None, update_module=False):
304         """ Return a registry for a given database name."""
305         with cls.lock():
306             try:
307                 return cls.registries[db_name]
308             except KeyError:
309                 return cls.new(db_name, force_demo, status,
310                                update_module)
311             finally:
312                 # set db tracker - cleaned up at the WSGI
313                 # dispatching phase in openerp.service.wsgi_server.application
314                 threading.current_thread().dbname = db_name
315
316     @classmethod
317     def new(cls, db_name, force_demo=False, status=None,
318             update_module=False):
319         """ Create and return a new registry for a given database name.
320
321         The (possibly) previous registry for that database name is discarded.
322
323         """
324         import openerp.modules
325         with cls.lock():
326             with openerp.api.Environment.manage():
327                 registry = Registry(db_name)
328
329                 # Initializing a registry will call general code which will in
330                 # turn call registries.get (this object) to obtain the registry
331                 # being initialized. Make it available in the registries
332                 # dictionary then remove it if an exception is raised.
333                 cls.delete(db_name)
334                 cls.registries[db_name] = registry
335                 try:
336                     with registry.cursor() as cr:
337                         seq_registry, seq_cache = Registry.setup_multi_process_signaling(cr)
338                         registry.base_registry_signaling_sequence = seq_registry
339                         registry.base_cache_signaling_sequence = seq_cache
340                     # This should be a method on Registry
341                     openerp.modules.load_modules(registry._db, force_demo, status, update_module)
342                 except Exception:
343                     del cls.registries[db_name]
344                     raise
345
346                 # load_modules() above can replace the registry by calling
347                 # indirectly new() again (when modules have to be uninstalled).
348                 # Yeah, crazy.
349                 registry = cls.registries[db_name]
350
351                 cr = registry.cursor()
352                 try:
353                     registry.do_parent_store(cr)
354                     cr.commit()
355                 finally:
356                     cr.close()
357
358         registry.ready = True
359
360         if update_module:
361             # only in case of update, otherwise we'll have an infinite reload loop!
362             cls.signal_registry_change(db_name)
363         return registry
364
365     @classmethod
366     def delete(cls, db_name):
367         """Delete the registry linked to a given database.  """
368         with cls.lock():
369             if db_name in cls.registries:
370                 cls.registries[db_name].clear_caches()
371                 del cls.registries[db_name]
372
373     @classmethod
374     def delete_all(cls):
375         """Delete all the registries. """
376         with cls.lock():
377             for db_name in cls.registries.keys():
378                 cls.delete(db_name)
379
380     @classmethod
381     def clear_caches(cls, db_name):
382         """Clear caches
383
384         This clears the caches associated to methods decorated with
385         ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models
386         of the given database name.
387
388         This method is given to spare you a ``RegistryManager.get(db_name)``
389         that would loads the given database if it was not already loaded.
390         """
391         with cls.lock():
392             if db_name in cls.registries:
393                 cls.registries[db_name].clear_caches()
394
395     @classmethod
396     def check_registry_signaling(cls, db_name):
397         """
398         Check if the modules have changed and performs all necessary operations to update
399         the registry of the corresponding database.
400
401
402         :returns: True if changes has been detected in the database and False otherwise.
403         """
404         changed = False
405         if openerp.multi_process and db_name in cls.registries:
406             registry = cls.get(db_name)
407             cr = registry.cursor()
408             try:
409                 cr.execute("""
410                     SELECT base_registry_signaling.last_value,
411                            base_cache_signaling.last_value
412                     FROM base_registry_signaling, base_cache_signaling""")
413                 r, c = cr.fetchone()
414                 _logger.debug("Multiprocess signaling check: [Registry - old# %s new# %s] "\
415                     "[Cache - old# %s new# %s]",
416                     registry.base_registry_signaling_sequence, r,
417                     registry.base_cache_signaling_sequence, c)
418                 # Check if the model registry must be reloaded (e.g. after the
419                 # database has been updated by another process).
420                 if registry.base_registry_signaling_sequence is not None and registry.base_registry_signaling_sequence != r:
421                     changed = True
422                     _logger.info("Reloading the model registry after database signaling.")
423                     registry = cls.new(db_name)
424                 # Check if the model caches must be invalidated (e.g. after a write
425                 # occured on another process). Don't clear right after a registry
426                 # has been reload.
427                 elif registry.base_cache_signaling_sequence is not None and registry.base_cache_signaling_sequence != c:
428                     changed = True
429                     _logger.info("Invalidating all model caches after database signaling.")
430                     registry.clear_caches()
431                     registry.reset_any_cache_cleared()
432                 registry.base_registry_signaling_sequence = r
433                 registry.base_cache_signaling_sequence = c
434             finally:
435                 cr.close()
436         return changed
437
438     @classmethod
439     def signal_caches_change(cls, db_name):
440         if openerp.multi_process and db_name in cls.registries:
441             # Check the registries if any cache has been cleared and signal it
442             # through the database to other processes.
443             registry = cls.get(db_name)
444             if registry.any_cache_cleared():
445                 _logger.info("At least one model cache has been cleared, signaling through the database.")
446                 cr = registry.cursor()
447                 r = 1
448                 try:
449                     cr.execute("select nextval('base_cache_signaling')")
450                     r = cr.fetchone()[0]
451                 finally:
452                     cr.close()
453                 registry.base_cache_signaling_sequence = r
454                 registry.reset_any_cache_cleared()
455
456     @classmethod
457     def signal_registry_change(cls, db_name):
458         if openerp.multi_process and db_name in cls.registries:
459             _logger.info("Registry changed, signaling through the database")
460             registry = cls.get(db_name)
461             cr = registry.cursor()
462             r = 1
463             try:
464                 cr.execute("select nextval('base_registry_signaling')")
465                 r = cr.fetchone()[0]
466             finally:
467                 cr.close()
468             registry.base_registry_signaling_sequence = r
469
470 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: