IdTool.py 9.58 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#                    Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

29
import random
30
from Acquisition import aq_base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31 32
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass, DTMLFile, PersistentMapping
33
from Products.ERP5Type.Tool.BaseTool import BaseTool
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34
from Products.ERP5Type import Permissions
35
from Products.CMFCore.utils import getToolByName
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36 37 38 39 40

from Products.ERP5 import _dtmldir

import threading

41
from BTrees.Length import Length
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42

43
class IdTool(BaseTool):
Vincent Pelletier's avatar
Vincent Pelletier committed
44 45 46 47 48 49 50 51 52 53 54 55
  """
    This tools handles the generation of IDs.
  """
  id = 'portal_ids'
  meta_type = 'ERP5 Id Tool'
  portal_type = 'Id Tool'

  # Declarative Security
  security = ClassSecurityInfo()

  security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
  manage_overview = DTMLFile( 'explainIdTool', _dtmldir )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56

57 58 59 60 61 62
  security.declareProtected(Permissions.AccessContentsInformation,
                            'getLastGeneratedId')
  def getLastGeneratedId(self,id_group=None,default=None):
    """
    Get the last id generated
    """
63
    if getattr(aq_base(self), 'dict_ids', None) is None:
64 65 66 67 68
      self.dict_ids = PersistentMapping()
    last_id = None
    if id_group is not None and id_group!='None':
      last_id = self.dict_ids.get(id_group, default)
    return last_id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
69
        
Jérome Perrin's avatar
Jérome Perrin committed
70
  security.declareProtected(Permissions.ModifyPortalContent,
71
                            'setLastGeneratedId')
72
  def setLastGeneratedId(self,new_id,id_group=None):
73 74 75 76
    """
    Set a new last id. This is usefull in order to reset
    a sequence of ids.
    """
77
    if getattr(aq_base(self), 'dict_ids', None) is None:
78 79 80 81 82 83 84 85 86 87 88
      self.dict_ids = PersistentMapping()
    if id_group is not None and id_group!='None':
      l = threading.Lock()
      l.acquire()
      try:
        self.dict_ids[id_group] = new_id
      finally:
        l.release()
        
  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewId')
Vincent Pelletier's avatar
Vincent Pelletier committed
89 90
  def generateNewId(self, id_group=None, default=None, method=None):
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
91
      Generate a new Id
Vincent Pelletier's avatar
Vincent Pelletier committed
92
    """
93
    
94
    if getattr(aq_base(self), 'dict_ids', None) is None:
Vincent Pelletier's avatar
Vincent Pelletier committed
95 96 97 98 99 100 101 102 103
      self.dict_ids = PersistentMapping()

    new_id = None
    if id_group is not None and id_group!='None':
      # Getting the last id
      last_id = None
      l = threading.Lock()
      l.acquire()
      try:
104 105 106 107 108 109 110 111 112
        class Dummy:
          pass
        dummy = Dummy()
        last_id = self.dict_ids.get(id_group, dummy)
        if last_id is dummy:
          if default is None:
            new_id=0
          else:
            new_id=default
113 114
          if method is not None:
            new_id=method(new_id)  
Vincent Pelletier's avatar
Vincent Pelletier committed
115
        else:
116 117 118 119 120
          # Now generate a new id
          if method is not None:
            new_id = method(last_id)
          else:
            new_id = last_id + 1
Vincent Pelletier's avatar
Vincent Pelletier committed
121 122 123 124 125 126 127
 
        # Store the new value
        self.dict_ids[id_group] = new_id
      finally:
        l.release()

    return new_id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
128

129 130 131 132
  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewLongId')
  def generateNewLongId(self, **kw):
    """
133
      Returns a random 64bits long.
134 135
      It's a 64bits number, so it can look ugly and/or huge to users.
    """
136
    return random.getrandbits(64)
137 138

  security.declareProtected(Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
139
                            'getDictLengthIdsItems')
140 141 142 143 144 145 146 147 148 149
  def getDictLengthIdsItems(self):
    """
      Return a copy of dict_length_ids.
      This is a workaround to access the persistent mapping content from ZSQL
      method to be able to insert initial tuples in the database at creation.
    """
    if getattr(self, 'dict_length_ids', None) is None:
      self.dict_length_ids = PersistentMapping()
    return self.dict_length_ids.items()

150 151 152 153 154 155 156
  security.declarePrivate('dumpDictLengthIdsItems')
  def dumpDictLengthIdsItems(self):
    """
      Store persistently data from SQL table portal_ids.
    """
    portal_catalog = getToolByName(self, 'portal_catalog').getSQLCatalog()
    query = getattr(portal_catalog, 'z_portal_ids_dump')
157
    dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None)
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
    if dict_length_ids is None:
      dict_length_ids = self.dict_length_ids = PersistentMapping()
    for line in query().dictionaries():
      id_group = line['id_group']
      last_id = line['last_id']
      stored_last_id = self.dict_length_ids.get(id_group)
      if stored_last_id is None:
        self.dict_length_ids[id_group] = Length(last_id)
      else:
        stored_last_id_value = stored_last_id()
        if stored_last_id_value < last_id:
          stored_last_id.set(last_id)
        else:
          if stored_last_id_value > last_id:
            LOG('IdTool', WARNING, 'ZODB value (%r) for group %r is higher ' \
                'than SQL value (%r). Keeping ZODB value untouched.' % \
                (stored_last_id, id_group, last_id))

176
  security.declareProtected(Permissions.AccessContentsInformation,
Aurel's avatar
Aurel committed
177
                            'getLastLengthGeneratedId')
Aurel's avatar
Aurel committed
178
  def getLastLengthGeneratedId(self, id_group, default=None):
179 180 181 182
    """
    Get the last length id generated
    """
    # check in persistent mapping if exists
183
    if getattr(aq_base(self), 'dict_length_ids', None) is not None:
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
      last_id = self.dict_length_ids.get(id_group)
      if last_id is not None:
        return last_id.value - 1
    # otherwise check in mysql
    portal_catalog = getToolByName(self, 'portal_catalog').getSQLCatalog()
    query = getattr(portal_catalog, 'z_portal_ids_get_last_id', None)
    if query is None:
      raise AttributeError, 'Error while getting last Id: ' \
            'z_portal_ids_get_last_id could not ' \
            'be found.'
    result = query(id_group=id_group)
    if len(result):
      return result[0]['last_id'] - 1
    return default

199
  security.declareProtected(Permissions.AccessContentsInformation,
200
                            'generateNewLengthIdList')
201
  def generateNewLengthIdList(self, id_group=None, id_count=1, default=None,
202
                              store=True):
203
    """
204 205
      Generates a list of Ids.
      The ids are generated using mysql and then stored in a Length object in a
206
      persistant mapping to be persistent.
207
      We use MySQL to generate IDs, because it is atomic and we don't want
208
      to generate any conflict at zope level. The possible downfall is that
209 210 211
      some IDs might be skipped because of failed transactions.
      "Length" is because the id is stored in a python object inspired by
      BTrees.Length. It doesn't have to be a length.
212

213
      store : if we want to store the new id into the zodb, we want it
214
              by default
215 216
    """
    new_id = None
217 218 219 220
    if id_group in (None, 'None'):
      raise ValueError, '%s is not a valid group Id.' % (repr(id_group), )
    if not isinstance(id_group, str):
      id_group = repr(id_group)
221
    if default is None:
222 223 224 225 226 227 228 229 230 231 232
      default = 1
    # FIXME: A skin folder should be used to contain ZSQLMethods instead of
    # default catalog, like activity tool (anyway, it uses activity tool
    # ZSQLConnection, so hot reindexing is not helping here).
    portal_catalog = getToolByName(self, 'portal_catalog').getSQLCatalog()
    query = getattr(portal_catalog, 'z_portal_ids_generate_id')
    commit = getattr(portal_catalog, 'z_portal_ids_commit')
    if None in (query, commit):
      raise AttributeError, 'Error while generating Id: ' \
        'z_portal_ids_generate_id and/or z_portal_ids_commit could not ' \
        'be found.'
233 234 235 236
    try:
      result = query(id_group=id_group, id_count=id_count, default=default)
    finally:
      commit()
237
    new_id = result[0]['LAST_INSERT_ID()']
238
    if store:
239
      if getattr(aq_base(self), 'dict_length_ids', None) is None:
240 241 242 243 244
        # Length objects are stored in a persistent mapping: there is one
        # Length object per id_group.
        self.dict_length_ids = PersistentMapping()
      if self.dict_length_ids.get(id_group) is None:
        self.dict_length_ids[id_group] = Length(new_id)
245
      self.dict_length_ids[id_group].set(new_id)
246 247 248 249
    return range(new_id - id_count, new_id)

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewLengthId')
250
  def generateNewLengthId(self, id_group=None, default=None, store=1):
251 252 253 254
    """
      Generates an Id.
      See generateNewLengthIdList documentation for details.
    """
255 256
    return self.generateNewLengthIdList(id_group=id_group, id_count=1, 
        default=default, store=store)[0]
257

Jean-Paul Smets's avatar
Jean-Paul Smets committed
258
InitializeClass(IdTool)