IdTool.py 9.3 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
Ivan Tyagov's avatar
Ivan Tyagov committed
36
from zLOG import LOG, WARNING
Jean-Paul Smets's avatar
Jean-Paul Smets committed
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
  security.declareProtected(Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
130
                            'getDictLengthIdsItems')
131 132 133 134 135 136 137 138 139 140
  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()

141 142 143 144 145 146 147
  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')
148
    dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None)
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    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))

167
  security.declareProtected(Permissions.AccessContentsInformation,
Aurel's avatar
Aurel committed
168
                            'getLastLengthGeneratedId')
Aurel's avatar
Aurel committed
169
  def getLastLengthGeneratedId(self, id_group, default=None):
170 171 172 173
    """
    Get the last length id generated
    """
    # check in persistent mapping if exists
174
    if getattr(aq_base(self), 'dict_length_ids', None) is not None:
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
      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

190
  security.declareProtected(Permissions.AccessContentsInformation,
191
                            'generateNewLengthIdList')
192
  def generateNewLengthIdList(self, id_group=None, id_count=1, default=None,
193
                              store=True):
194
    """
195 196
      Generates a list of Ids.
      The ids are generated using mysql and then stored in a Length object in a
197
      persistant mapping to be persistent.
198
      We use MySQL to generate IDs, because it is atomic and we don't want
199
      to generate any conflict at zope level. The possible downfall is that
200 201 202
      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.
203

204
      store : if we want to store the new id into the zodb, we want it
205
              by default
206 207
    """
    new_id = None
208 209 210 211
    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)
212
    if default is None:
213 214 215 216 217 218 219 220 221 222 223
      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.'
224 225 226 227
    try:
      result = query(id_group=id_group, id_count=id_count, default=default)
    finally:
      commit()
228
    new_id = result[0]['LAST_INSERT_ID()']
229
    if store:
230
      if getattr(aq_base(self), 'dict_length_ids', None) is None:
231 232 233 234 235
        # 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)
236
      self.dict_length_ids[id_group].set(new_id)
237 238 239 240
    return range(new_id - id_count, new_id)

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewLengthId')
241
  def generateNewLengthId(self, id_group=None, default=None, store=1):
242 243 244 245
    """
      Generates an Id.
      See generateNewLengthIdList documentation for details.
    """
246 247
    return self.generateNewLengthIdList(id_group=id_group, id_count=1, 
        default=default, store=store)[0]
248

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