hooks/syncsources.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2010-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """hooks for repository sources synchronization"""
       
    19 
       
    20 from cubicweb import _
       
    21 
       
    22 from socket import gethostname
       
    23 
       
    24 from logilab.common.decorators import clear_cache
       
    25 
       
    26 from cubicweb import validation_error
       
    27 from cubicweb.predicates import is_instance
       
    28 from cubicweb.server import SOURCE_TYPES, hook
       
    29 
       
    30 class SourceHook(hook.Hook):
       
    31     __abstract__ = True
       
    32     category = 'cw.sources'
       
    33 
       
    34 
       
    35 # repo sources synchronization #################################################
       
    36 
       
    37 class SourceAddedOp(hook.Operation):
       
    38     entity = None # make pylint happy
       
    39     def postcommit_event(self):
       
    40         self.cnx.repo.add_source(self.entity)
       
    41 
       
    42 class SourceAddedHook(SourceHook):
       
    43     __regid__ = 'cw.sources.added'
       
    44     __select__ = SourceHook.__select__ & is_instance('CWSource')
       
    45     events = ('after_add_entity',)
       
    46     def __call__(self):
       
    47         try:
       
    48             sourcecls = SOURCE_TYPES[self.entity.type]
       
    49         except KeyError:
       
    50             msg = _('Unknown source type')
       
    51             raise validation_error(self.entity, {('type', 'subject'): msg})
       
    52         # ignore creation of the system source done during database
       
    53         # initialisation, as config for this source is in a file and handling
       
    54         # is done separatly (no need for the operation either)
       
    55         if self.entity.name != 'system':
       
    56             sourcecls.check_conf_dict(self.entity.eid, self.entity.host_config,
       
    57                                       fail_if_unknown=not self._cw.vreg.config.repairing)
       
    58             SourceAddedOp(self._cw, entity=self.entity)
       
    59 
       
    60 
       
    61 class SourceRemovedOp(hook.Operation):
       
    62     uri = None # make pylint happy
       
    63     def postcommit_event(self):
       
    64         self.cnx.repo.remove_source(self.uri)
       
    65 
       
    66 class SourceRemovedHook(SourceHook):
       
    67     __regid__ = 'cw.sources.removed'
       
    68     __select__ = SourceHook.__select__ & is_instance('CWSource')
       
    69     events = ('before_delete_entity',)
       
    70     def __call__(self):
       
    71         if self.entity.name == 'system':
       
    72             msg = _("You cannot remove the system source")
       
    73             raise validation_error(self.entity, {None: msg})
       
    74         SourceRemovedOp(self._cw, uri=self.entity.name)
       
    75 
       
    76 
       
    77 class SourceConfigUpdatedOp(hook.DataOperationMixIn, hook.Operation):
       
    78 
       
    79     def precommit_event(self):
       
    80         self.__processed = []
       
    81         for source in self.get_data():
       
    82             if not self.cnx.deleted_in_transaction(source.eid):
       
    83                 conf = source.repo_source.check_config(source)
       
    84                 self.__processed.append( (source, conf) )
       
    85 
       
    86     def postcommit_event(self):
       
    87         for source, conf in self.__processed:
       
    88             source.repo_source.update_config(source, conf)
       
    89 
       
    90 
       
    91 class SourceRenamedOp(hook.LateOperation):
       
    92     oldname = newname = None # make pylint happy
       
    93 
       
    94     def precommit_event(self):
       
    95         source = self.cnx.repo.sources_by_uri[self.oldname]
       
    96         sql = 'UPDATE entities SET asource=%(newname)s WHERE asource=%(oldname)s'
       
    97         self.cnx.system_sql(sql, {'oldname': self.oldname,
       
    98                                       'newname': self.newname})
       
    99 
       
   100     def postcommit_event(self):
       
   101         repo = self.cnx.repo
       
   102         # XXX race condition
       
   103         source = repo.sources_by_uri.pop(self.oldname)
       
   104         source.uri = self.newname
       
   105         source.public_config['uri'] = self.newname
       
   106         repo.sources_by_uri[self.newname] = source
       
   107         repo._type_source_cache.clear()
       
   108         clear_cache(repo, 'source_defs')
       
   109 
       
   110 
       
   111 class SourceUpdatedHook(SourceHook):
       
   112     __regid__ = 'cw.sources.configupdate'
       
   113     __select__ = SourceHook.__select__ & is_instance('CWSource')
       
   114     events = ('before_update_entity',)
       
   115     def __call__(self):
       
   116         if 'name' in self.entity.cw_edited:
       
   117             oldname, newname = self.entity.cw_edited.oldnewvalue('name')
       
   118             if oldname == 'system':
       
   119                 msg = _("You cannot rename the system source")
       
   120                 raise validation_error(self.entity, {('name', 'subject'): msg})
       
   121             SourceRenamedOp(self._cw, oldname=oldname, newname=newname)
       
   122         if 'config' in self.entity.cw_edited or 'url' in self.entity.cw_edited:
       
   123             if self.entity.name == 'system' and self.entity.config:
       
   124                 msg = _("Configuration of the system source goes to "
       
   125                         "the 'sources' file, not in the database")
       
   126                 raise validation_error(self.entity, {('config', 'subject'): msg})
       
   127             SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity)
       
   128 
       
   129 
       
   130 class SourceHostConfigUpdatedHook(SourceHook):
       
   131     __regid__ = 'cw.sources.hostconfigupdate'
       
   132     __select__ = SourceHook.__select__ & is_instance('CWSourceHostConfig')
       
   133     events = ('after_add_entity', 'after_update_entity', 'before_delete_entity',)
       
   134     def __call__(self):
       
   135         if self.entity.match(gethostname()):
       
   136             if self.event == 'after_update_entity' and \
       
   137                    not 'config' in self.entity.cw_edited:
       
   138                 return
       
   139             try:
       
   140                 SourceConfigUpdatedOp.get_instance(self._cw).add_data(self.entity.cwsource)
       
   141             except IndexError:
       
   142                 # XXX no source linked to the host config yet
       
   143                 pass
       
   144 
       
   145 
       
   146 # source mapping synchronization ###############################################
       
   147 #
       
   148 # Expect cw_for_source/cw_schema are immutable relations (i.e. can't change from
       
   149 # a source or schema to another).
       
   150 
       
   151 class SourceMappingImmutableHook(SourceHook):
       
   152     """check cw_for_source and cw_schema are immutable relations
       
   153 
       
   154     XXX empty delete perms would be enough?
       
   155     """
       
   156     __regid__ = 'cw.sources.mapping.immutable'
       
   157     __select__ = SourceHook.__select__ & hook.match_rtype('cw_for_source', 'cw_schema')
       
   158     events = ('before_add_relation',)
       
   159     def __call__(self):
       
   160         if not self._cw.added_in_transaction(self.eidfrom):
       
   161             msg = _("You can't change this relation")
       
   162             raise validation_error(self.eidfrom, {self.rtype: msg})
       
   163 
       
   164 
       
   165 class SourceMappingChangedOp(hook.DataOperationMixIn, hook.Operation):
       
   166     def check_or_update(self, checkonly):
       
   167         cnx = self.cnx
       
   168         # take care, can't call get_data() twice
       
   169         try:
       
   170             data = self.__data
       
   171         except AttributeError:
       
   172             data = self.__data = self.get_data()
       
   173         for schemacfg, source in data:
       
   174             if source is None:
       
   175                 source = schemacfg.cwsource.repo_source
       
   176             if cnx.added_in_transaction(schemacfg.eid):
       
   177                 if not cnx.deleted_in_transaction(schemacfg.eid):
       
   178                     source.add_schema_config(schemacfg, checkonly=checkonly)
       
   179             elif cnx.deleted_in_transaction(schemacfg.eid):
       
   180                 source.del_schema_config(schemacfg, checkonly=checkonly)
       
   181             else:
       
   182                 source.update_schema_config(schemacfg, checkonly=checkonly)
       
   183 
       
   184     def precommit_event(self):
       
   185         self.check_or_update(True)
       
   186 
       
   187     def postcommit_event(self):
       
   188         self.check_or_update(False)
       
   189 
       
   190 
       
   191 class SourceMappingChangedHook(SourceHook):
       
   192     __regid__ = 'cw.sources.schemaconfig'
       
   193     __select__ = SourceHook.__select__ & is_instance('CWSourceSchemaConfig')
       
   194     events = ('after_add_entity', 'after_update_entity')
       
   195     def __call__(self):
       
   196         if self.event == 'after_add_entity' or (
       
   197             self.event == 'after_update_entity' and 'options' in self.entity.cw_edited):
       
   198             SourceMappingChangedOp.get_instance(self._cw).add_data(
       
   199                 (self.entity, None) )
       
   200 
       
   201 class SourceMappingDeleteHook(SourceHook):
       
   202     __regid__ = 'cw.sources.delschemaconfig'
       
   203     __select__ = SourceHook.__select__ & hook.match_rtype('cw_for_source')
       
   204     events = ('before_delete_relation',)
       
   205     def __call__(self):
       
   206         SourceMappingChangedOp.get_instance(self._cw).add_data(
       
   207             (self._cw.entity_from_eid(self.eidfrom),
       
   208              self._cw.entity_from_eid(self.eidto).repo_source) )