|
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) ) |