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