# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""Server subcube of cubicweb : defines objects used only on the server(repository) sideThe server module contains functions to initialize a new repository."""__docformat__="restructuredtext en"importsysfromos.pathimportjoin,existsfromglobimportglobfromlogilab.common.modutilsimportLazyObjectfromlogilab.common.textutilsimportsplitstripfromlogilab.common.registryimportyesfromlogilabimportdatabasefromyamsimportBASE_GROUPSfromcubicwebimportCW_SOFTWARE_ROOTfromcubicweb.appobjectimportAppObjectclassShuttingDown(BaseException):"""raised when trying to access some resources while the repository is shutting down. Inherit from BaseException so that `except Exception` won't catch it. """# server-side services #########################################################classService(AppObject):"""Base class for services. A service is a selectable object that performs an action server-side. Use :class:`cubicweb.dbapi.Connection.call_service` to call them from the web-side. When inheriting this class, do not forget to define at least the __regid__ attribute (and probably __select__ too). """__registry__='services'__select__=yes()defcall(self,**kwargs):raiseNotImplementedError# server-side debugging ######################################################### server debugging flags. They may be combined using binary operators.#:no debug informationDBG_NONE=0#: no debug information#: rql execution informationDBG_RQL=1#: executed sqlDBG_SQL=2#: repository eventsDBG_REPO=4#: multi-sourcesDBG_MS=8#: hooksDBG_HOOKS=16#: operationsDBG_OPS=32#: more verbosityDBG_MORE=64#: all level enabledDBG_ALL=DBG_RQL+DBG_SQL+DBG_REPO+DBG_MS+DBG_HOOKS+DBG_OPS+DBG_MORE#: current debug modeDEBUG=0defset_debug(debugmode):"""change the repository debugging mode"""globalDEBUGifnotdebugmode:DEBUG=0returnifisinstance(debugmode,basestring):formodeinsplitstrip(debugmode,sep='|'):DEBUG|=globals()[mode]else:DEBUG|=debugmodeclassdebugged(object):"""Context manager and decorator to help debug the repository. It can be used either as a context manager: >>> with debugged(server.DBG_RQL | server.DBG_REPO): ... # some code in which you want to debug repository activity, ... # seing information about RQL being executed an repository events. or as a function decorator: >>> @debugged(server.DBG_RQL | server.DBG_REPO) ... def some_function(): ... # some code in which you want to debug repository activity, ... # seing information about RQL being executed an repository events The debug mode will be reset to its original value when leaving the "with" block or the decorated function. """def__init__(self,debugmode):self.debugmode=debugmodeself._clevel=Nonedef__enter__(self):"""enter with block"""self._clevel=DEBUGset_debug(self.debugmode)def__exit__(self,exctype,exc,traceback):"""leave with block"""set_debug(self._clevel)returntracebackisNonedef__call__(self,func):"""decorate function"""defwrapped(*args,**kwargs):_clevel=DEBUGset_debug(self.debugmode)try:returnfunc(*args,**kwargs)finally:set_debug(self._clevel)returnwrapped# database initialization ######################################################defcreate_user(session,login,pwd,*groups):# monkey patch this method if you want to customize admin/anon creation# (that maybe necessary if you change CWUser's schema)user=session.create_entity('CWUser',login=login,upassword=pwd)forgroupingroups:session.execute('SET U in_group G WHERE U eid %(u)s, G name %(group)s',{'u':user.eid,'group':group})returnuserdefinit_repository(config,interactive=True,drop=False,vreg=None):"""initialise a repository database by creating tables add filling them with the minimal set of entities (ie at least the schema, base groups and a initial user) """fromcubicweb.dbapiimportin_memory_repo_cnxfromcubicweb.server.repositoryimportRepositoryfromcubicweb.server.utilsimportmanager_userpasswdfromcubicweb.server.sqlutilsimportsqlexec,sqlschema,sql_drop_all_user_tablesfromcubicweb.server.sqlutilsimport_SQL_DROP_ALL_USER_TABLES_FILTER_FUNCTIONasdrop_filter# configuration to avoid db schema loading and user'state checking# on connectionconfig.creating=Trueconfig.consider_user_state=Falseconfig.cubicweb_appobject_path=set(('hooks','entities'))config.cube_appobject_path=set(('hooks','entities'))# only enable the system source at initialization timerepo=Repository(config,vreg=vreg)schema=repo.schemasourcescfg=config.sources()source=sourcescfg['system']driver=source['db-driver']sqlcnx=repo.system_source.get_connection()sqlcursor=sqlcnx.cursor()execute=sqlcursor.executeifdrop:helper=database.get_db_helper(driver)dropsql=sql_drop_all_user_tables(helper,sqlcursor)# We may fail dropping some tables because of table dependencies, in a first pass.# So, we try a second drop sequence to drop remaining tables if needed.# Note that 2 passes is an arbitrary choice as it seems enougth for our usecases.# (looping may induce infinite recursion when user have no right for example)# Here we try to keep code simple and backend independant. That why we don't try to# distinguish remaining tables (wrong right, dependencies, ...).failed=sqlexec(dropsql,execute,cnx=sqlcnx,pbtitle='-> dropping tables (first pass)')iffailed:failed=sqlexec(failed,execute,cnx=sqlcnx,pbtitle='-> dropping tables (second pass)')remainings=filter(drop_filter,helper.list_tables(sqlcursor))assertnotremainings,'Remaining tables: %s'%', '.join(remainings)_title='-> creating tables 'print_title,# schema entities and relations tables# can't skip entities table even if system source doesn't support them,# they are used sometimes by generated sql. Keeping them empty is much# simpler than fixing this...schemasql=sqlschema(schema,driver)#skip_entities=[str(e) for e in schema.entities()# if not repo.system_source.support_entity(str(e))])sqlexec(schemasql,execute,pbtitle=_title,delimiter=';;')sqlcursor.close()sqlcnx.commit()sqlcnx.close()session=repo.internal_session()# insert entity representing the system sourcessource=session.create_entity('CWSource',type=u'native',name=u'system')repo.system_source.eid=ssource.eidsession.execute('SET X cw_source X WHERE X eid %(x)s',{'x':ssource.eid})# insert base groups and default adminprint'-> inserting default user and default groups.'try:login=unicode(sourcescfg['admin']['login'])pwd=sourcescfg['admin']['password']exceptKeyError:ifinteractive:msg='enter login and password of the initial manager account'login,pwd=manager_userpasswd(msg=msg,confirm=True)else:login,pwd=unicode(source['db-user']),source['db-password']# sort for eid predicatability as expected in some server testsforgroupinsorted(BASE_GROUPS):session.create_entity('CWGroup',name=unicode(group))admin=create_user(session,login,pwd,'managers')session.execute('SET X owned_by U WHERE X is IN (CWGroup,CWSource), U eid %(u)s',{'u':admin.eid})session.commit()session.close()repo.shutdown()# reloging using the admin userconfig._cubes=None# avoid assertion errorrepo,cnx=in_memory_repo_cnx(config,login,password=pwd)repo.system_source.eid=ssource.eid# redo this manuallyassertlen(repo.sources)==1,repo.sourceshandler=config.migration_handler(schema,interactive=False,cnx=cnx,repo=repo)# install additional driver specific sql fileshandler.cmd_install_custom_sql_scripts()forcubeinreversed(config.cubes()):handler.cmd_install_custom_sql_scripts(cube)# serialize the schemainitialize_schema(config,schema,handler)# yoo !cnx.commit()repo.system_source.init_creating()cnx.commit()cnx.close()repo.shutdown()# restore initial configurationconfig.creating=Falseconfig.consider_user_state=True# (drop instance attribute to get back to class attribute)delconfig.cubicweb_appobject_pathdelconfig.cube_appobject_pathprint'-> database for instance %s initialized.'%config.appiddefinitialize_schema(config,schema,mhandler,event='create'):fromcubicweb.server.schemaserialimportserialize_schemasession=mhandler.sessioncubes=config.cubes()# deactivate every hooks but those responsible to set metadata# so, NO INTEGRITY CHECKS are done, to have quicker db creation.# Active integrity is kept else we may pb such as two default# workflows for one entity type.withsession.deny_all_hooks_but('metadata','activeintegrity'):# execute cubicweb's pre<event> scriptmhandler.cmd_exec_event_script('pre%s'%event)# execute cubes pre<event> script if anyforcubeinreversed(cubes):mhandler.cmd_exec_event_script('pre%s'%event,cube)# execute instance's pre<event> script (useful in tests)mhandler.cmd_exec_event_script('pre%s'%event,apphome=True)# enter instance'schema into the databasesession.set_cnxset()serialize_schema(session,schema)# execute cubicweb's post<event> scriptmhandler.cmd_exec_event_script('post%s'%event)# execute cubes'post<event> script if anyforcubeinreversed(cubes):mhandler.cmd_exec_event_script('post%s'%event,cube)# execute instance's post<event> script (useful in tests)mhandler.cmd_exec_event_script('post%s'%event,apphome=True)# sqlite'stored procedures have to be registered at connection opening timefromlogilab.databaseimportSQL_CONNECT_HOOKS# add to this set relations which should have their add security checking done# *BEFORE* adding the actual relation (done after by default)BEFORE_ADD_RELATIONS=set(('owned_by',))# add to this set relations which should have their add security checking done# *at COMMIT TIME* (done after by default)ON_COMMIT_ADD_RELATIONS=set(())# available sources registrySOURCE_TYPES={'native':LazyObject('cubicweb.server.sources.native','NativeSQLSource'),'datafeed':LazyObject('cubicweb.server.sources.datafeed','DataFeedSource'),'ldapfeed':LazyObject('cubicweb.server.sources.ldapfeed','LDAPFeedSource'),'ldapuser':LazyObject('cubicweb.server.sources.ldapuser','LDAPUserSource'),'pyrorql':LazyObject('cubicweb.server.sources.pyrorql','PyroRQLSource'),'zmqrql':LazyObject('cubicweb.server.sources.zmqrql','ZMQRQLSource'),}