111 self.support_relations['identity'] = False |
111 self.support_relations['identity'] = False |
112 self.eid = None |
112 self.eid = None |
113 self.public_config = source_config.copy() |
113 self.public_config = source_config.copy() |
114 self.remove_sensitive_information(self.public_config) |
114 self.remove_sensitive_information(self.public_config) |
115 |
115 |
116 def init_creating(self): |
|
117 """method called by the repository once ready to create a new instance""" |
|
118 pass |
|
119 |
|
120 def init(self, activated, session=None): |
|
121 """method called by the repository once ready to handle request. |
|
122 `activated` is a boolean flag telling if the source is activated or not. |
|
123 """ |
|
124 pass |
|
125 |
|
126 def backup(self, backupfile, confirm): |
|
127 """method called to create a backup of source's data""" |
|
128 pass |
|
129 |
|
130 def restore(self, backupfile, confirm, drop): |
|
131 """method called to restore a backup of source's data""" |
|
132 pass |
|
133 |
|
134 def close_pool_connections(self): |
|
135 for pool in self.repo.pools: |
|
136 pool._cursors.pop(self.uri, None) |
|
137 pool.source_cnxs[self.uri][1].close() |
|
138 |
|
139 def open_pool_connections(self): |
|
140 for pool in self.repo.pools: |
|
141 pool.source_cnxs[self.uri] = (self, self.get_connection()) |
|
142 |
|
143 def reset_caches(self): |
|
144 """method called during test to reset potential source caches""" |
|
145 pass |
|
146 |
|
147 def clear_eid_cache(self, eid, etype): |
|
148 """clear potential caches for the given eid""" |
|
149 pass |
|
150 |
|
151 def __repr__(self): |
116 def __repr__(self): |
152 return '<%s source %s @%#x>' % (self.uri, self.eid, id(self)) |
117 return '<%s source %s @%#x>' % (self.uri, self.eid, id(self)) |
153 |
118 |
154 def __cmp__(self, other): |
119 def __cmp__(self, other): |
155 """simple comparison function to get predictable source order, with the |
120 """simple comparison function to get predictable source order, with the |
161 return 1 |
126 return 1 |
162 if other.uri == 'system': |
127 if other.uri == 'system': |
163 return -1 |
128 return -1 |
164 return cmp(self.uri, other.uri) |
129 return cmp(self.uri, other.uri) |
165 |
130 |
|
131 def backup(self, backupfile, confirm): |
|
132 """method called to create a backup of source's data""" |
|
133 pass |
|
134 |
|
135 def restore(self, backupfile, confirm, drop): |
|
136 """method called to restore a backup of source's data""" |
|
137 pass |
|
138 |
|
139 # source initialization / finalization ##################################### |
|
140 |
166 def set_schema(self, schema): |
141 def set_schema(self, schema): |
167 """set the instance'schema""" |
142 """set the instance'schema""" |
168 self.schema = schema |
143 self.schema = schema |
|
144 |
|
145 def init_creating(self): |
|
146 """method called by the repository once ready to create a new instance""" |
|
147 pass |
|
148 |
|
149 def init(self, activated, session=None): |
|
150 """method called by the repository once ready to handle request. |
|
151 `activated` is a boolean flag telling if the source is activated or not. |
|
152 """ |
|
153 pass |
|
154 |
|
155 PUBLIC_KEYS = ('type', 'uri') |
|
156 def remove_sensitive_information(self, sourcedef): |
|
157 """remove sensitive information such as login / password from source |
|
158 definition |
|
159 """ |
|
160 for key in sourcedef.keys(): |
|
161 if not key in self.PUBLIC_KEYS: |
|
162 sourcedef.pop(key) |
|
163 |
|
164 # connections handling ##################################################### |
|
165 |
|
166 def get_connection(self): |
|
167 """open and return a connection to the source""" |
|
168 raise NotImplementedError() |
|
169 |
|
170 def check_connection(self, cnx): |
|
171 """Check connection validity, return None if the connection is still |
|
172 valid else a new connection (called when the pool using the given |
|
173 connection is being attached to a session). Do nothing by default. |
|
174 """ |
|
175 pass |
|
176 |
|
177 def close_pool_connections(self): |
|
178 for pool in self.repo.pools: |
|
179 pool._cursors.pop(self.uri, None) |
|
180 pool.source_cnxs[self.uri][1].close() |
|
181 |
|
182 def open_pool_connections(self): |
|
183 for pool in self.repo.pools: |
|
184 pool.source_cnxs[self.uri] = (self, self.get_connection()) |
|
185 |
|
186 def pool_reset(self, cnx): |
|
187 """the pool using the given connection is being reseted from its current |
|
188 attached session |
|
189 |
|
190 do nothing by default |
|
191 """ |
|
192 pass |
|
193 |
|
194 # cache handling ########################################################### |
|
195 |
|
196 def reset_caches(self): |
|
197 """method called during test to reset potential source caches""" |
|
198 pass |
|
199 |
|
200 def clear_eid_cache(self, eid, etype): |
|
201 """clear potential caches for the given eid""" |
|
202 pass |
|
203 |
|
204 # external source api ###################################################### |
|
205 |
|
206 def eid2extid(self, eid, session=None): |
|
207 return self.repo.eid2extid(self, eid, session) |
|
208 |
|
209 def extid2eid(self, value, etype, session=None, **kwargs): |
|
210 return self.repo.extid2eid(self, value, etype, session, **kwargs) |
169 |
211 |
170 def support_entity(self, etype, write=False): |
212 def support_entity(self, etype, write=False): |
171 """return true if the given entity's type is handled by this adapter |
213 """return true if the given entity's type is handled by this adapter |
172 if write is true, return true only if it's a RW support |
214 if write is true, return true only if it's a RW support |
173 """ |
215 """ |
219 # card 1 relation ? ...) |
261 # card 1 relation ? ...) |
220 if self.support_relation(rtype): |
262 if self.support_relation(rtype): |
221 return rtype in self.cross_relations |
263 return rtype in self.cross_relations |
222 return rtype not in self.dont_cross_relations |
264 return rtype not in self.dont_cross_relations |
223 |
265 |
224 def eid2extid(self, eid, session=None): |
266 def before_entity_insertion(self, session, lid, etype, eid): |
225 return self.repo.eid2extid(self, eid, session) |
267 """called by the repository when an eid has been attributed for an |
226 |
268 entity stored here but the entity has not been inserted in the system |
227 def extid2eid(self, value, etype, session=None, **kwargs): |
269 table yet. |
228 return self.repo.extid2eid(self, value, etype, session, **kwargs) |
270 |
229 |
271 This method must return the an Entity instance representation of this |
230 PUBLIC_KEYS = ('type', 'uri') |
272 entity. |
231 def remove_sensitive_information(self, sourcedef): |
273 """ |
232 """remove sensitive information such as login / password from source |
274 entity = self.repo.vreg['etypes'].etype_class(etype)(session) |
233 definition |
275 entity.eid = eid |
234 """ |
276 entity.cw_edited = EditedEntity(entity) |
235 for key in sourcedef.keys(): |
277 return entity |
236 if not key in self.PUBLIC_KEYS: |
278 |
237 sourcedef.pop(key) |
279 def after_entity_insertion(self, session, lid, entity): |
238 |
280 """called by the repository after an entity stored here has been |
239 def _cleanup_system_relations(self, session): |
281 inserted in the system table. |
240 """remove relation in the system source referencing entities coming from |
282 """ |
241 this source |
283 pass |
242 """ |
284 |
243 cu = session.system_sql('SELECT eid FROM entities WHERE source=%(uri)s', |
285 # user authentication api ################################################## |
244 {'uri': self.uri}) |
|
245 myeids = ','.join(str(r[0]) for r in cu.fetchall()) |
|
246 if not myeids: |
|
247 return |
|
248 # delete relations referencing one of those eids |
|
249 eidcolum = SQL_PREFIX + 'eid' |
|
250 for rschema in self.schema.relations(): |
|
251 if rschema.final or rschema.type in VIRTUAL_RTYPES: |
|
252 continue |
|
253 if rschema.inlined: |
|
254 column = SQL_PREFIX + rschema.type |
|
255 for subjtype in rschema.subjects(): |
|
256 table = SQL_PREFIX + str(subjtype) |
|
257 for objtype in rschema.objects(subjtype): |
|
258 if self.support_entity(objtype): |
|
259 sql = 'UPDATE %s SET %s=NULL WHERE %s IN (%s);' % ( |
|
260 table, column, eidcolum, myeids) |
|
261 session.system_sql(sql) |
|
262 break |
|
263 continue |
|
264 for etype in rschema.subjects(): |
|
265 if self.support_entity(etype): |
|
266 sql = 'DELETE FROM %s_relation WHERE eid_from IN (%s);' % ( |
|
267 rschema.type, myeids) |
|
268 session.system_sql(sql) |
|
269 break |
|
270 for etype in rschema.objects(): |
|
271 if self.support_entity(etype): |
|
272 sql = 'DELETE FROM %s_relation WHERE eid_to IN (%s);' % ( |
|
273 rschema.type, myeids) |
|
274 session.system_sql(sql) |
|
275 break |
|
276 |
|
277 def cleanup_entities_info(self, session): |
|
278 """cleanup system tables from information for entities coming from |
|
279 this source. This should be called when a source is removed to |
|
280 properly cleanup the database |
|
281 """ |
|
282 self._cleanup_system_relations(session) |
|
283 # fti / entities tables cleanup |
|
284 # sqlite doesn't support DELETE FROM xxx USING yyy |
|
285 dbhelper = session.pool.source('system').dbhelper |
|
286 session.system_sql('DELETE FROM %s WHERE %s.%s IN (SELECT eid FROM ' |
|
287 'entities WHERE entities.source=%%(uri)s)' |
|
288 % (dbhelper.fti_table, dbhelper.fti_table, |
|
289 dbhelper.fti_uid_attr), |
|
290 {'uri': self.uri}) |
|
291 session.system_sql('DELETE FROM entities WHERE source=%(uri)s', |
|
292 {'uri': self.uri}) |
|
293 |
|
294 # abstract methods to override (at least) in concrete source classes ####### |
|
295 |
|
296 def get_connection(self): |
|
297 """open and return a connection to the source""" |
|
298 raise NotImplementedError() |
|
299 |
|
300 def check_connection(self, cnx): |
|
301 """check connection validity, return None if the connection is still valid |
|
302 else a new connection (called when the pool using the given connection is |
|
303 being attached to a session) |
|
304 |
|
305 do nothing by default |
|
306 """ |
|
307 pass |
|
308 |
|
309 def pool_reset(self, cnx): |
|
310 """the pool using the given connection is being reseted from its current |
|
311 attached session |
|
312 |
|
313 do nothing by default |
|
314 """ |
|
315 pass |
|
316 |
286 |
317 def authenticate(self, session, login, **kwargs): |
287 def authenticate(self, session, login, **kwargs): |
318 """if the source support CWUser entity type, it should implement |
288 """if the source support CWUser entity type, it should implement |
319 this method which should return CWUser eid for the given login/password |
289 this method which should return CWUser eid for the given login/password |
320 if this account is defined in this source and valid login / password is |
290 if this account is defined in this source and valid login / password is |
321 given. Else raise `AuthenticationError` |
291 given. Else raise `AuthenticationError` |
322 """ |
292 """ |
323 raise NotImplementedError() |
293 raise NotImplementedError() |
|
294 |
|
295 # RQL query api ############################################################ |
324 |
296 |
325 def syntax_tree_search(self, session, union, |
297 def syntax_tree_search(self, session, union, |
326 args=None, cachekey=None, varmap=None, debug=0): |
298 args=None, cachekey=None, varmap=None, debug=0): |
327 """return result from this source for a rql query (actually from a rql |
299 """return result from this source for a rql query (actually from a rql |
328 syntax tree and a solution dictionary mapping each used variable to a |
300 syntax tree and a solution dictionary mapping each used variable to a |
338 .executemany(). |
310 .executemany(). |
339 """ |
311 """ |
340 res = self.syntax_tree_search(session, union, args, varmap=varmap) |
312 res = self.syntax_tree_search(session, union, args, varmap=varmap) |
341 session.pool.source('system').manual_insert(res, table, session) |
313 session.pool.source('system').manual_insert(res, table, session) |
342 |
314 |
343 # system source don't have to implement the two methods below |
315 # write modification api ################################################### |
344 |
|
345 def before_entity_insertion(self, session, lid, etype, eid): |
|
346 """called by the repository when an eid has been attributed for an |
|
347 entity stored here but the entity has not been inserted in the system |
|
348 table yet. |
|
349 |
|
350 This method must return the an Entity instance representation of this |
|
351 entity. |
|
352 """ |
|
353 entity = self.repo.vreg['etypes'].etype_class(etype)(session) |
|
354 entity.eid = eid |
|
355 entity.cw_edited = EditedEntity(entity) |
|
356 return entity |
|
357 |
|
358 def after_entity_insertion(self, session, lid, entity): |
|
359 """called by the repository after an entity stored here has been |
|
360 inserted in the system table. |
|
361 """ |
|
362 pass |
|
363 |
|
364 # read-only sources don't have to implement methods below |
316 # read-only sources don't have to implement methods below |
365 |
317 |
366 def get_extid(self, entity): |
318 def get_extid(self, entity): |
367 """return the external id for the given newly inserted entity""" |
319 """return the external id for the given newly inserted entity""" |
368 raise NotImplementedError() |
320 raise NotImplementedError() |