252 lines
8.8 KiB
Python
252 lines
8.8 KiB
Python
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
#this repository contains the full copyright notices and license terms.
|
|
"Objects Services"
|
|
|
|
from orm import ORM, OPERATORS
|
|
from trytond.netsvc import Service, LocalService
|
|
from trytond import pooler
|
|
import copy
|
|
import sys
|
|
from trytond.sql_db import IntegrityError
|
|
import traceback
|
|
from trytond.tools import Cache, find_language_context
|
|
import time
|
|
from threading import Lock
|
|
from trytond.config import CONFIG
|
|
import logging
|
|
|
|
MODULE_LIST = []
|
|
MODULE_CLASS_LIST = {}
|
|
|
|
|
|
class OSVService(Service):
|
|
|
|
def __init__(self):
|
|
self.object_name_pool = {}
|
|
self.module_obj_list = {}
|
|
Service.__init__(self, 'object_proxy')
|
|
Service.join_group(self, 'web-service')
|
|
Service.export_method(self, self.object_name_list)
|
|
Service.export_method(self, self.exec_workflow)
|
|
Service.export_method(self, self.execute)
|
|
Service.export_method(self, self.execute_cr)
|
|
self._sql_errors = {}
|
|
|
|
def execute_cr(self, cursor, user, object_name, method, *args, **kargs):
|
|
# TODO: check security level
|
|
try:
|
|
obj = pooler.get_pool(cursor.dbname).get(object_name)
|
|
if not obj:
|
|
raise Exception('Error',
|
|
'Object %s doesn\'t exist' % str(object_name))
|
|
if method not in obj._rpc_allowed:
|
|
raise Exception('Error',
|
|
'Calling method %s on object %s is not allowed' \
|
|
% (method, object_name))
|
|
res = getattr(obj, method)(cursor, user, *args, **kargs)
|
|
return res
|
|
except Exception, exception:
|
|
if CONFIG['verbose']:
|
|
tb_s = reduce(lambda x, y: x+y,
|
|
traceback.format_exception(*sys.exc_info()))
|
|
logger = logging.getLogger("web-service")
|
|
logger.error('Exception in call: \n' + tb_s)
|
|
if isinstance(exception, IntegrityError):
|
|
for key in self._sql_errors.keys():
|
|
if key in exception[0]:
|
|
msg = self._sql_errors[key]
|
|
cursor2 = pooler.get_db(cursor.dbname).cursor()
|
|
try:
|
|
cursor2.execute('SELECT value ' \
|
|
'FROM ir_translation ' \
|
|
'WHERE lang=%s ' \
|
|
'AND type=%s ' \
|
|
'AND src=%s',
|
|
(find_language_context(args), 'error',
|
|
msg))
|
|
if cursor2.rowcount:
|
|
res = cursor2.fetchone()[0]
|
|
if res:
|
|
msg = res
|
|
finally:
|
|
cursor2.close()
|
|
raise Exception('UserError', 'Constraint Error',
|
|
msg)
|
|
raise
|
|
|
|
def execute(self, dbname, user, object_name, method, *args, **kargs):
|
|
database, pool = pooler.get_db_and_pool(dbname)
|
|
cursor = database.cursor()
|
|
# TODO add retry when exception for concurency update
|
|
try:
|
|
try:
|
|
res = pool.execute_cr(cursor, user, object_name, method,
|
|
*args, **kargs)
|
|
cursor.commit()
|
|
except Exception:
|
|
cursor.rollback()
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
return res
|
|
|
|
def exec_workflow_cr(self, cursor, user, object_name, method, object_id,
|
|
*args):
|
|
wf_service = LocalService("workflow")
|
|
try:
|
|
wf_service.trg_validate(user, object_name, object_id, method,
|
|
cursor, *args)
|
|
except Exception, exception:
|
|
if CONFIG['verbose']:
|
|
tb_s = reduce(lambda x, y: x+y,
|
|
traceback.format_exception(*sys.exc_info()))
|
|
logger = logging.getLogger()
|
|
logger.error('Exception in call: \n' + tb_s)
|
|
if isinstance(exception, IntegrityError):
|
|
for key in self._sql_errors.keys():
|
|
if key in exception[0]:
|
|
raise Exception('UserError', 'Constraint Error',
|
|
self._sql_errors[key])
|
|
raise
|
|
return True
|
|
|
|
def exec_workflow(self, dbname, user, object_name, method, object_id,
|
|
*args):
|
|
cursor = pooler.get_db(dbname).cursor()
|
|
# TODO add retry when exception for concurency update
|
|
try:
|
|
try:
|
|
res = self.exec_workflow_cr(cursor, user, object_name, method,
|
|
object_id, *args)
|
|
cursor.commit()
|
|
except Exception:
|
|
cursor.rollback()
|
|
raise
|
|
finally:
|
|
cursor.close()
|
|
return res
|
|
|
|
def object_name_list(self):
|
|
return self.object_name_pool.keys()
|
|
|
|
def add(self, name, object_name_inst):
|
|
"""
|
|
adds a new obj instance to the obj pool.
|
|
if it already existed, the instance is replaced
|
|
"""
|
|
if self.object_name_pool.has_key(name):
|
|
del self.object_name_pool[name]
|
|
self.object_name_pool[name] = object_name_inst
|
|
|
|
module = str(object_name_inst.__class__)[6:]
|
|
module = module[:len(module)-1]
|
|
module = module.split('.')[0][2:]
|
|
self.module_obj_list.setdefault(module, []).append(object_name_inst)
|
|
|
|
def get(self, name):
|
|
return self.object_name_pool[name]
|
|
|
|
def instanciate(self, module):
|
|
res = []
|
|
class_list = MODULE_CLASS_LIST.get(module, [])
|
|
for klass in class_list:
|
|
inst = klass.create_instance(self, module)
|
|
res.append(inst)
|
|
for key, _, msg in inst._sql_constraints:
|
|
self._sql_errors[inst._table + '_' + key] = msg
|
|
for key in inst._sql_error_messages.keys():
|
|
self._sql_errors[inst._table + '_' + key] = \
|
|
inst._sql_error_messages[key]
|
|
return res
|
|
|
|
|
|
class OSV(ORM):
|
|
|
|
def __new__(cls):
|
|
for module in cls.__module__.split('.'):
|
|
if module != 'trytond' and module != 'modules':
|
|
break
|
|
if not hasattr(cls, '_module'):
|
|
cls._module = module
|
|
MODULE_CLASS_LIST.setdefault(cls._module, []).append(cls)
|
|
if module not in MODULE_LIST:
|
|
MODULE_LIST.append(cls._module)
|
|
return None
|
|
|
|
def create_instance(cls, pool, module):
|
|
"""
|
|
try to apply inheritancy at the instanciation level and
|
|
put objs in the pool var
|
|
"""
|
|
if cls._name in pool.object_name_list():
|
|
parent_class = pool.get(cls._name).__class__
|
|
cls = type(cls._name, (cls, parent_class), {})
|
|
|
|
obj = object.__new__(cls)
|
|
pool.add(obj._name, obj)
|
|
obj.pool = pool
|
|
obj.__init__()
|
|
return obj
|
|
|
|
create_instance = classmethod(create_instance)
|
|
|
|
def __init__(self):
|
|
super(OSV, self).__init__()
|
|
|
|
|
|
class Cacheable(object):
|
|
|
|
def __init__(self):
|
|
super(Cacheable, self).__init__()
|
|
self._cache = {}
|
|
self.name = self._table
|
|
self.timestamp = None
|
|
self.max_len = 1024
|
|
self.timeout = 3600
|
|
self.lock = Lock()
|
|
Cache._cache_instance.append(self)
|
|
|
|
def add(self, cursor, key, value):
|
|
self.lock.acquire()
|
|
try:
|
|
self._cache.setdefault(cursor.dbname, {})
|
|
|
|
lower = None
|
|
if len(self._cache[cursor.dbname]) > self.max_len:
|
|
mintime = time.time() - self.timeout
|
|
for key2 in self._cache[cursor.dbname].keys():
|
|
last_time = self._cache[cursor.dbname][key2][1]
|
|
if mintime > last_time:
|
|
del self._cache[cursor.dbname][key2]
|
|
else:
|
|
if not lower or lower[1] > last_time:
|
|
lower = (key2, last_time)
|
|
if len(self._cache[cursor.dbname]) > self.max_len and lower:
|
|
del self._cache[cursor.dbname][lower[0]]
|
|
|
|
self._cache[cursor.dbname][key] = (value, time.time())
|
|
finally:
|
|
self.lock.release()
|
|
|
|
def invalidate(self, cursor, key):
|
|
self.lock.acquire()
|
|
try:
|
|
del self._cache[cursor.dbname][key]
|
|
finally:
|
|
self.lock.release()
|
|
|
|
def get(self, cursor, key):
|
|
try:
|
|
return self._cache[cursor.dbname][key][0]
|
|
except KeyError:
|
|
return None
|
|
|
|
def clear(self, cursor):
|
|
self.lock.acquire()
|
|
try:
|
|
self._cache.setdefault(cursor.dbname, {})
|
|
self._cache[cursor.dbname].clear()
|
|
Cache.reset(cursor.dbname, self.name)
|
|
finally:
|
|
self.lock.release()
|