36 |
36 |
37 from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, |
37 from cubicweb import (CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, |
38 UnknownEid, AuthenticationError, ExecutionError, |
38 UnknownEid, AuthenticationError, ExecutionError, |
39 ETypeNotSupportedBySources, MultiSourcesError, |
39 ETypeNotSupportedBySources, MultiSourcesError, |
40 BadConnectionId, Unauthorized, ValidationError, |
40 BadConnectionId, Unauthorized, ValidationError, |
41 typed_eid) |
41 typed_eid, onevent) |
42 from cubicweb import cwvreg, schema, server |
42 from cubicweb import cwvreg, schema, server |
43 from cubicweb.server import utils, hook, pool, querier, sources |
43 from cubicweb.server import utils, hook, pool, querier, sources |
44 from cubicweb.server.session import Session, InternalSession, security_enabled |
44 from cubicweb.server.session import Session, InternalSession, InternalManager, \ |
45 |
45 security_enabled |
46 |
|
47 class CleanupEidTypeCacheOp(hook.SingleLastOperation): |
|
48 """on rollback of a insert query or commit of delete query, we have to |
|
49 clear repository's cache from no more valid entries |
|
50 |
|
51 NOTE: querier's rqlst/solutions cache may have been polluted too with |
|
52 queries such as Any X WHERE X eid 32 if 32 has been rollbacked however |
|
53 generated queries are unpredictable and analysing all the cache probably |
|
54 too expensive. Notice that there is no pb when using args to specify eids |
|
55 instead of giving them into the rql string. |
|
56 """ |
|
57 |
|
58 def commit_event(self): |
|
59 """the observed connections pool has been rollbacked, |
|
60 remove inserted eid from repository type/source cache |
|
61 """ |
|
62 try: |
|
63 self.session.repo.clear_caches( |
|
64 self.session.transaction_data['pendingeids']) |
|
65 except KeyError: |
|
66 pass |
|
67 |
|
68 def rollback_event(self): |
|
69 """the observed connections pool has been rollbacked, |
|
70 remove inserted eid from repository type/source cache |
|
71 """ |
|
72 try: |
|
73 self.session.repo.clear_caches( |
|
74 self.session.transaction_data['neweids']) |
|
75 except KeyError: |
|
76 pass |
|
77 |
46 |
78 |
47 |
79 def del_existing_rel_if_needed(session, eidfrom, rtype, eidto): |
48 def del_existing_rel_if_needed(session, eidfrom, rtype, eidto): |
80 """delete existing relation when adding a new one if card is 1 or ? |
49 """delete existing relation when adding a new one if card is 1 or ? |
81 |
50 |
162 # cache (extid, source uri) -> eid |
131 # cache (extid, source uri) -> eid |
163 self._extid_cache = {} |
132 self._extid_cache = {} |
164 # open some connections pools |
133 # open some connections pools |
165 if config.open_connections_pools: |
134 if config.open_connections_pools: |
166 self.open_connections_pools() |
135 self.open_connections_pools() |
|
136 @onevent('after-registry-reload', self) |
|
137 def fix_user_classes(self): |
|
138 usercls = self.vreg['etypes'].etype_class('CWUser') |
|
139 for session in self._sessions.values(): |
|
140 if not isinstance(session.user, InternalManager): |
|
141 session.user.__class__ = usercls |
167 |
142 |
168 def _bootstrap_hook_registry(self): |
143 def _bootstrap_hook_registry(self): |
169 """called during bootstrap since we need the metadata hooks""" |
144 """called during bootstrap since we need the metadata hooks""" |
170 hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks') |
145 hooksdirectory = join(CW_SOFTWARE_ROOT, 'hooks') |
171 self.vreg.init_registration([hooksdirectory]) |
146 self.vreg.init_registration([hooksdirectory]) |
528 """ |
504 """ |
529 session = self.internal_session() |
505 session = self.internal_session() |
530 # for consistency, keep same error as unique check hook (although not required) |
506 # for consistency, keep same error as unique check hook (although not required) |
531 errmsg = session._('the value "%s" is already used, use another one') |
507 errmsg = session._('the value "%s" is already used, use another one') |
532 try: |
508 try: |
533 if (session.execute('CWUser X WHERE X login %(login)s', {'login': login}) |
509 if (session.execute('CWUser X WHERE X login %(login)s', {'login': login}, |
|
510 build_descr=False) |
534 or session.execute('CWUser X WHERE X use_email C, C address %(login)s', |
511 or session.execute('CWUser X WHERE X use_email C, C address %(login)s', |
535 {'login': login})): |
512 {'login': login}, build_descr=False)): |
536 qname = role_name('login', 'subject') |
513 qname = role_name('login', 'subject') |
537 raise ValidationError(None, {qname: errmsg % login}) |
514 raise ValidationError(None, {qname: errmsg % login}) |
538 # we have to create the user |
515 # we have to create the user |
539 user = self.vreg['etypes'].etype_class('CWUser')(session, None) |
516 user = self.vreg['etypes'].etype_class('CWUser')(session) |
540 if isinstance(password, unicode): |
517 if isinstance(password, unicode): |
541 # password should *always* be utf8 encoded |
518 # password should *always* be utf8 encoded |
542 password = password.encode('UTF8') |
519 password = password.encode('UTF8') |
543 kwargs['login'] = login |
520 kwargs['login'] = login |
544 kwargs['upassword'] = password |
521 kwargs['upassword'] = password |
546 self.glob_add_entity(session, user) |
523 self.glob_add_entity(session, user) |
547 session.execute('SET X in_group G WHERE X eid %(x)s, G name "users"', |
524 session.execute('SET X in_group G WHERE X eid %(x)s, G name "users"', |
548 {'x': user.eid}) |
525 {'x': user.eid}) |
549 if email or '@' in login: |
526 if email or '@' in login: |
550 d = {'login': login, 'email': email or login} |
527 d = {'login': login, 'email': email or login} |
551 if session.execute('EmailAddress X WHERE X address %(email)s', d): |
528 if session.execute('EmailAddress X WHERE X address %(email)s', d, |
|
529 build_descr=False): |
552 qname = role_name('address', 'subject') |
530 qname = role_name('address', 'subject') |
553 raise ValidationError(None, {qname: errmsg % d['email']}) |
531 raise ValidationError(None, {qname: errmsg % d['email']}) |
554 session.execute('INSERT EmailAddress X: X address %(email)s, ' |
532 session.execute('INSERT EmailAddress X: X address %(email)s, ' |
555 'U primary_email X, U use_email X ' |
533 'U primary_email X, U use_email X ' |
556 'WHERE U login %(login)s', d) |
534 'WHERE U login %(login)s', d, build_descr=False) |
557 session.commit() |
535 session.commit() |
558 finally: |
536 finally: |
559 session.close() |
537 session.close() |
560 return True |
538 return True |
561 |
539 |
931 def add_info(self, session, entity, source, extid=None, complete=True): |
909 def add_info(self, session, entity, source, extid=None, complete=True): |
932 """add type and source info for an eid into the system table, |
910 """add type and source info for an eid into the system table, |
933 and index the entity with the full text index |
911 and index the entity with the full text index |
934 """ |
912 """ |
935 # begin by inserting eid/type/source/extid into the entities table |
913 # begin by inserting eid/type/source/extid into the entities table |
936 new = session.transaction_data.setdefault('neweids', set()) |
914 hook.set_operation(session, 'neweids', entity.eid, |
937 new.add(entity.eid) |
915 hook.CleanupNewEidsCacheOp) |
938 self.system_source.add_info(session, entity, source, extid, complete) |
916 self.system_source.add_info(session, entity, source, extid, complete) |
939 CleanupEidTypeCacheOp(session) |
|
940 |
917 |
941 def delete_info(self, session, entity, sourceuri, extid): |
918 def delete_info(self, session, entity, sourceuri, extid): |
942 """called by external source when some entity known by the system source |
919 """called by external source when some entity known by the system source |
943 has been deleted in the external source |
920 has been deleted in the external source |
944 """ |
921 """ |
945 self._prepare_delete_info(session, entity, sourceuri) |
922 # mark eid as being deleted in session info and setup cache update |
|
923 # operation |
|
924 hook.set_operation(session, 'pendingeids', entity.eid, |
|
925 hook.CleanupDeletedEidsCacheOp) |
946 self._delete_info(session, entity, sourceuri, extid) |
926 self._delete_info(session, entity, sourceuri, extid) |
947 |
|
948 def _prepare_delete_info(self, session, entity, sourceuri): |
|
949 """prepare the repository for deletion of an entity: |
|
950 * update the fti |
|
951 * mark eid as being deleted in session info |
|
952 * setup cache update operation |
|
953 * if undoable, get back all entity's attributes and relation |
|
954 """ |
|
955 eid = entity.eid |
|
956 self.system_source.fti_unindex_entity(session, eid) |
|
957 pending = session.transaction_data.setdefault('pendingeids', set()) |
|
958 pending.add(eid) |
|
959 CleanupEidTypeCacheOp(session) |
|
960 |
927 |
961 def _delete_info(self, session, entity, sourceuri, extid): |
928 def _delete_info(self, session, entity, sourceuri, extid): |
962 # attributes=None, relations=None): |
929 # attributes=None, relations=None): |
963 """delete system information on deletion of an entity: |
930 """delete system information on deletion of an entity: |
964 * delete all remaining relations from/to this entity |
931 * delete all remaining relations from/to this entity |
975 if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes: |
942 if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes: |
976 continue |
943 continue |
977 if role == 'subject': |
944 if role == 'subject': |
978 # don't skip inlined relation so they are regularly |
945 # don't skip inlined relation so they are regularly |
979 # deleted and so hooks are correctly called |
946 # deleted and so hooks are correctly called |
980 selection = 'X %s Y' % rtype |
947 rql = 'DELETE X %s Y WHERE X eid %%(x)s' % rtype |
981 else: |
948 else: |
982 selection = 'Y %s X' % rtype |
949 rql = 'DELETE Y %s X WHERE X eid %%(x)s' % rtype |
983 rql = 'DELETE %s WHERE X eid %%(x)s' % selection |
|
984 session.execute(rql, {'x': eid}, 'x', build_descr=False) |
950 session.execute(rql, {'x': eid}, 'x', build_descr=False) |
985 self.system_source.delete_info(session, entity, sourceuri, extid) |
951 self.system_source.delete_info(session, entity, sourceuri, extid) |
986 |
952 |
987 def locate_relation_source(self, session, subject, rtype, object): |
953 def locate_relation_source(self, session, subject, rtype, object): |
988 subjsource = self.source_from_eid(subject, session) |
954 subjsource = self.source_from_eid(subject, session) |
1009 if source.support_entity(etype, 1): |
975 if source.support_entity(etype, 1): |
1010 return source |
976 return source |
1011 else: |
977 else: |
1012 raise ETypeNotSupportedBySources(etype) |
978 raise ETypeNotSupportedBySources(etype) |
1013 |
979 |
|
980 def init_entity_caches(self, session, entity, source): |
|
981 """add entity to session entities cache and repo's extid cache. |
|
982 Return entity's ext id if the source isn't the system source. |
|
983 """ |
|
984 session.set_entity_cache(entity) |
|
985 suri = source.uri |
|
986 if suri == 'system': |
|
987 extid = None |
|
988 else: |
|
989 extid = source.get_extid(entity) |
|
990 self._extid_cache[(str(extid), suri)] = entity.eid |
|
991 self._type_source_cache[entity.eid] = (entity.__regid__, suri, extid) |
|
992 return extid |
|
993 |
1014 def glob_add_entity(self, session, entity): |
994 def glob_add_entity(self, session, entity): |
1015 """add an entity to the repository |
995 """add an entity to the repository |
1016 |
996 |
1017 the entity eid should originaly be None and a unique eid is assigned to |
997 the entity eid should originaly be None and a unique eid is assigned to |
1018 the entity instance |
998 the entity instance |
1024 # XXX kill that transmutation feature ! |
1004 # XXX kill that transmutation feature ! |
1025 if not entity_ is entity: |
1005 if not entity_ is entity: |
1026 entity.__class__ = entity_.__class__ |
1006 entity.__class__ = entity_.__class__ |
1027 entity.__dict__.update(entity_.__dict__) |
1007 entity.__dict__.update(entity_.__dict__) |
1028 eschema = entity.e_schema |
1008 eschema = entity.e_schema |
1029 etype = str(eschema) |
1009 source = self.locate_etype_source(entity.__regid__) |
1030 source = self.locate_etype_source(etype) |
1010 # allocate an eid to the entity before calling hooks |
1031 # attribute an eid to the entity before calling hooks |
|
1032 entity.set_eid(self.system_source.create_eid(session)) |
1011 entity.set_eid(self.system_source.create_eid(session)) |
|
1012 # set caches asap |
|
1013 extid = self.init_entity_caches(session, entity, source) |
1033 if server.DEBUG & server.DBG_REPO: |
1014 if server.DEBUG & server.DBG_REPO: |
1034 print 'ADD entity', etype, entity.eid, dict(entity) |
1015 print 'ADD entity', entity.__regid__, entity.eid, dict(entity) |
1035 relations = [] |
1016 relations = [] |
1036 if source.should_call_hooks: |
1017 if source.should_call_hooks: |
1037 self.hm.call_hooks('before_add_entity', session, entity=entity) |
1018 self.hm.call_hooks('before_add_entity', session, entity=entity) |
1038 # XXX use entity.keys here since edited_attributes is not updated for |
1019 # XXX use entity.keys here since edited_attributes is not updated for |
1039 # inline relations |
1020 # inline relations XXX not true, right? (see edited_attributes |
|
1021 # affectation above) |
1040 for attr in entity.iterkeys(): |
1022 for attr in entity.iterkeys(): |
1041 rschema = eschema.subjrels[attr] |
1023 rschema = eschema.subjrels[attr] |
1042 if not rschema.final: # inlined relation |
1024 if not rschema.final: # inlined relation |
1043 relations.append((attr, entity[attr])) |
1025 relations.append((attr, entity[attr])) |
1044 entity.set_defaults() |
1026 entity.set_defaults() |
1045 if session.is_hook_category_activated('integrity'): |
1027 if session.is_hook_category_activated('integrity'): |
1046 entity.check(creation=True) |
1028 entity.check(creation=True) |
1047 source.add_entity(session, entity) |
1029 source.add_entity(session, entity) |
1048 if source.uri != 'system': |
|
1049 extid = source.get_extid(entity) |
|
1050 self._extid_cache[(str(extid), source.uri)] = entity.eid |
|
1051 else: |
|
1052 extid = None |
|
1053 self.add_info(session, entity, source, extid, complete=False) |
1030 self.add_info(session, entity, source, extid, complete=False) |
1054 entity._is_saved = True # entity has an eid and is saved |
1031 entity._is_saved = True # entity has an eid and is saved |
1055 # prefill entity relation caches |
1032 # prefill entity relation caches |
1056 session.set_entity_cache(entity) |
|
1057 for rschema in eschema.subject_relations(): |
1033 for rschema in eschema.subject_relations(): |
1058 rtype = str(rschema) |
1034 rtype = str(rschema) |
1059 if rtype in schema.VIRTUAL_RTYPES: |
1035 if rtype in schema.VIRTUAL_RTYPES: |
1060 continue |
1036 continue |
1061 if rschema.final: |
1037 if rschema.final: |
1083 |
1059 |
1084 def glob_update_entity(self, session, entity, edited_attributes): |
1060 def glob_update_entity(self, session, entity, edited_attributes): |
1085 """replace an entity in the repository |
1061 """replace an entity in the repository |
1086 the type and the eid of an entity must not be changed |
1062 the type and the eid of an entity must not be changed |
1087 """ |
1063 """ |
1088 etype = str(entity.e_schema) |
|
1089 if server.DEBUG & server.DBG_REPO: |
1064 if server.DEBUG & server.DBG_REPO: |
1090 print 'UPDATE entity', etype, entity.eid, \ |
1065 print 'UPDATE entity', entity.__regid__, entity.eid, \ |
1091 dict(entity), edited_attributes |
1066 dict(entity), edited_attributes |
1092 entity.edited_attributes = edited_attributes |
1067 entity.edited_attributes = edited_attributes |
1093 if session.is_hook_category_activated('integrity'): |
1068 if session.is_hook_category_activated('integrity'): |
1094 entity.check() |
1069 entity.check() |
1095 eschema = entity.e_schema |
1070 eschema = entity.e_schema |
1148 |
1123 |
1149 def glob_delete_entity(self, session, eid): |
1124 def glob_delete_entity(self, session, eid): |
1150 """delete an entity and all related entities from the repository""" |
1125 """delete an entity and all related entities from the repository""" |
1151 entity = session.entity_from_eid(eid) |
1126 entity = session.entity_from_eid(eid) |
1152 etype, sourceuri, extid = self.type_and_source_from_eid(eid, session) |
1127 etype, sourceuri, extid = self.type_and_source_from_eid(eid, session) |
1153 self._prepare_delete_info(session, entity, sourceuri) |
|
1154 if server.DEBUG & server.DBG_REPO: |
1128 if server.DEBUG & server.DBG_REPO: |
1155 print 'DELETE entity', etype, eid |
1129 print 'DELETE entity', etype, eid |
1156 source = self.sources_by_uri[sourceuri] |
1130 source = self.sources_by_uri[sourceuri] |
1157 if source.should_call_hooks: |
1131 if source.should_call_hooks: |
1158 self.hm.call_hooks('before_delete_entity', session, entity=entity) |
1132 self.hm.call_hooks('before_delete_entity', session, entity=entity) |