| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | ## |
|---|
| 3 | ## |
|---|
| 4 | ## This file is part of CDS Indico. |
|---|
| 5 | ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN. |
|---|
| 6 | ## |
|---|
| 7 | ## CDS Indico is free software; you can redistribute it and/or |
|---|
| 8 | ## modify it under the terms of the GNU General Public License as |
|---|
| 9 | ## published by the Free Software Foundation; either version 2 of the |
|---|
| 10 | ## License, or (at your option) any later version. |
|---|
| 11 | ## |
|---|
| 12 | ## CDS Indico is distributed in the hope that it will be useful, but |
|---|
| 13 | ## WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 14 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 15 | ## General Public License for more details. |
|---|
| 16 | ## |
|---|
| 17 | ## You should have received a copy of the GNU General Public License |
|---|
| 18 | ## along with CDS Indico; if not, write to the Free Software Foundation, Inc., |
|---|
| 19 | ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 20 | |
|---|
| 21 | """This file contains the implementation of some classes dealing with the DB |
|---|
| 22 | so it's usage is easier and more transparent to the rest of the application |
|---|
| 23 | """ |
|---|
| 24 | import os |
|---|
| 25 | import sys |
|---|
| 26 | import threading |
|---|
| 27 | import pkg_resources |
|---|
| 28 | from contextlib import contextmanager |
|---|
| 29 | |
|---|
| 30 | from ZEO.ClientStorage import ClientStorage |
|---|
| 31 | from ZODB.DB import DB |
|---|
| 32 | import transaction |
|---|
| 33 | import ZODB |
|---|
| 34 | |
|---|
| 35 | from MaKaC.consoleScripts.installBase import getIndicoInstallMode |
|---|
| 36 | skip_imports = getIndicoInstallMode() |
|---|
| 37 | |
|---|
| 38 | if not skip_imports: |
|---|
| 39 | from MaKaC.common.logger import Logger |
|---|
| 40 | from MaKaC.i18n import _ |
|---|
| 41 | |
|---|
| 42 | |
|---|
| 43 | class MaKaCDB(DB): |
|---|
| 44 | """Subclass of ZODB.DB necessary to remove possible existing dependencies |
|---|
| 45 | from IC""" |
|---|
| 46 | |
|---|
| 47 | def classFactory(self, connection, modulename, globalname): |
|---|
| 48 | if globalname=="PersistentMapping": |
|---|
| 49 | modulename="persistent.mapping" |
|---|
| 50 | elif globalname=="PersistentList": |
|---|
| 51 | modulename="persistent.list" |
|---|
| 52 | elif modulename.startswith("IndexedCatalog.BTrees."): |
|---|
| 53 | modulename="BTrees.%s"%modulename[22:] |
|---|
| 54 | return DB.classFactory(self, connection, modulename, globalname) |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | class DBMgr: |
|---|
| 58 | """This class provides the access point to the Shelf (every client will |
|---|
| 59 | use this class in order to obtain a shelf) and some mechanism to |
|---|
| 60 | ensure there is only one connection opened to the DB during a single |
|---|
| 61 | request. |
|---|
| 62 | This class must not be instantiated, an instance can be obtained |
|---|
| 63 | though the "getInstance" method (implements the singleton pattern |
|---|
| 64 | to ensure unicity) |
|---|
| 65 | Needs to be checked if the class (static) attribute _instance is |
|---|
| 66 | thread-safe: as it is shared by all the objects of this class, |
|---|
| 67 | it could provoke concurrency troubles having 2 threads using the db |
|---|
| 68 | connection at the same time. However, the model under which we are |
|---|
| 69 | programming is not multi-threaded (mod_python seems to run different |
|---|
| 70 | interpreters for each apache subprocess, see mod_python doc section |
|---|
| 71 | 4.1) so this thechnique can be used. This has to be taken into account |
|---|
| 72 | when migrating the system to a multi-threading environment. |
|---|
| 73 | """ |
|---|
| 74 | _instance = None |
|---|
| 75 | |
|---|
| 76 | def __init__( self, hostname=None, port=None ): |
|---|
| 77 | import Configuration # Please leave this import here, db.py is imported during installation process |
|---|
| 78 | cfg = Configuration.Config.getInstance() |
|---|
| 79 | |
|---|
| 80 | if not hostname: |
|---|
| 81 | hostname = cfg.getDBConnectionParams()[0] |
|---|
| 82 | if not port: |
|---|
| 83 | port = cfg.getDBConnectionParams()[1] |
|---|
| 84 | |
|---|
| 85 | self._storage=ClientStorage((hostname, port), username=cfg.getDBUserName(), password=cfg.getDBPassword(), realm=cfg.getDBRealm()) |
|---|
| 86 | self._db=MaKaCDB(self._storage) |
|---|
| 87 | self._conn={} |
|---|
| 88 | |
|---|
| 89 | @classmethod |
|---|
| 90 | def getInstance( cls, *args, **kwargs ): |
|---|
| 91 | if cls._instance == None: |
|---|
| 92 | Logger.get('dbmgr').debug('cls._instance is None') |
|---|
| 93 | cls._instance=DBMgr(*args, **kwargs) |
|---|
| 94 | return cls._instance |
|---|
| 95 | |
|---|
| 96 | @classmethod |
|---|
| 97 | def setInstance( cls, dbInstance ): |
|---|
| 98 | cls._instance = dbInstance |
|---|
| 99 | |
|---|
| 100 | @staticmethod |
|---|
| 101 | def _getUniqueIdentifier(): |
|---|
| 102 | return threading._get_ident() |
|---|
| 103 | |
|---|
| 104 | def _getConnObject(self): |
|---|
| 105 | tid = DBMgr._getUniqueIdentifier() |
|---|
| 106 | return self._conn[tid] |
|---|
| 107 | |
|---|
| 108 | def _setConnObject(self, obj): |
|---|
| 109 | tid = DBMgr._getUniqueIdentifier() |
|---|
| 110 | self._conn[tid] = obj |
|---|
| 111 | |
|---|
| 112 | def _delConnObject(self): |
|---|
| 113 | tid = DBMgr._getUniqueIdentifier() |
|---|
| 114 | del self._conn[tid] |
|---|
| 115 | |
|---|
| 116 | def startRequest( self ): |
|---|
| 117 | """Initialise the DB and starts a new transaction. |
|---|
| 118 | """ |
|---|
| 119 | |
|---|
| 120 | tid = DBMgr._getUniqueIdentifier() |
|---|
| 121 | |
|---|
| 122 | self._conn[tid] = self._db.open() |
|---|
| 123 | Logger.get('dbmgr').debug('Allocated connection for %s - table size is %s' % \ |
|---|
| 124 | (tid, len(self._conn))) |
|---|
| 125 | |
|---|
| 126 | def endRequest( self, commit=True ): |
|---|
| 127 | """Closes the DB and commits changes. |
|---|
| 128 | """ |
|---|
| 129 | if commit: |
|---|
| 130 | self.commit() |
|---|
| 131 | else: |
|---|
| 132 | self.abort() |
|---|
| 133 | |
|---|
| 134 | #modification vendredi 010907 |
|---|
| 135 | # try: |
|---|
| 136 | # self._conn.close() |
|---|
| 137 | # self._conn=None |
|---|
| 138 | # except: |
|---|
| 139 | # pass |
|---|
| 140 | |
|---|
| 141 | self._getConnObject().close() |
|---|
| 142 | self._delConnObject() |
|---|
| 143 | |
|---|
| 144 | def getDBConnection( self ): |
|---|
| 145 | return self._getConnObject() |
|---|
| 146 | |
|---|
| 147 | def isConnected( self ): |
|---|
| 148 | tid = DBMgr._getUniqueIdentifier() |
|---|
| 149 | |
|---|
| 150 | return tid in self._conn |
|---|
| 151 | |
|---|
| 152 | def getDBConnCache(self): |
|---|
| 153 | conn = self._getConnObject() |
|---|
| 154 | return conn._cache |
|---|
| 155 | |
|---|
| 156 | def getDBClassFactory(self): |
|---|
| 157 | return self._db.classFactory |
|---|
| 158 | |
|---|
| 159 | def commit(self, sub=False): |
|---|
| 160 | if (sub): |
|---|
| 161 | transaction.savepoint() |
|---|
| 162 | else: |
|---|
| 163 | transaction.commit() |
|---|
| 164 | |
|---|
| 165 | def commitZODBOld(self, sub=False): |
|---|
| 166 | transaction.commit(sub) |
|---|
| 167 | |
|---|
| 168 | def abort(self): |
|---|
| 169 | transaction.abort() |
|---|
| 170 | |
|---|
| 171 | def sync(self): |
|---|
| 172 | self._getConnObject().sync() |
|---|
| 173 | |
|---|
| 174 | def pack( self, days=1 ): |
|---|
| 175 | self._storage.pack(days=days) |
|---|
| 176 | |
|---|
| 177 | def undoInfo(self, stepNumber=0): |
|---|
| 178 | # One step is made of 1000 transactions. First step is 0 and returns |
|---|
| 179 | # transactions 0 to 999. |
|---|
| 180 | return self._db.undoInfo(stepNumber*1000, (stepNumber+1)*1000) |
|---|
| 181 | |
|---|
| 182 | def undo(self, trans_id): |
|---|
| 183 | self._db.undo(trans_id) |
|---|
| 184 | |
|---|
| 185 | def getDBSize( self ): |
|---|
| 186 | """Return an approximate size of the database, in bytes.""" |
|---|
| 187 | return self._storage.getSize() |
|---|
| 188 | |
|---|
| 189 | def loadObject(self, oid, version): |
|---|
| 190 | return self._storage.load(oid, version) |
|---|
| 191 | |
|---|
| 192 | def storeObject(self, oid, serial, data, version, trans): |
|---|
| 193 | return self._storage.store(oid, serial, data, version, trans) |
|---|
| 194 | |
|---|
| 195 | def tpcBegin(self, trans): |
|---|
| 196 | self._storage.tpc_begin(trans) |
|---|
| 197 | |
|---|
| 198 | def tpcVote(self, trans): |
|---|
| 199 | self._storage.tpc_vote(trans) |
|---|
| 200 | |
|---|
| 201 | def tpcFinish(self, trans): |
|---|
| 202 | self._storage.tpc_finish(trans) |
|---|
| 203 | |
|---|
| 204 | @contextmanager |
|---|
| 205 | def transaction(self): |
|---|
| 206 | """ |
|---|
| 207 | context manager (`with`) |
|---|
| 208 | """ |
|---|
| 209 | yield |
|---|
| 210 | self.commit() |
|---|
| 211 | |
|---|
| 212 | # ZODB version check |
|---|
| 213 | try: |
|---|
| 214 | zodbPkg = pkg_resources.require('ZODB3')[0] |
|---|
| 215 | zodbVersion = zodbPkg.parsed_version |
|---|
| 216 | zodbVersion = (int(zodbVersion[0]), int(zodbVersion[1])) |
|---|
| 217 | except pkg_resources.DistributionNotFound: |
|---|
| 218 | # Very old versions, in which ZODB didn't register |
|---|
| 219 | # with pkg_resources |
|---|
| 220 | import ZODB |
|---|
| 221 | zodbVersion = ZODB.__version__.split('.') |
|---|
| 222 | |
|---|
| 223 | if int(zodbVersion[0]) < 3: |
|---|
| 224 | raise Exception("ZODB 3 required! %s found" % zodbPkg.version) |
|---|
| 225 | elif int(zodbVersion[1]) < 7: |
|---|
| 226 | commit = commitZODBOld |
|---|