IdTool.py 8.83 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
from Products.ERP5 import _dtmldir

39
from BTrees.Length import Length
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40

41
class IdTool(BaseTool):
Vincent Pelletier's avatar
Vincent Pelletier committed
42 43 44 45 46 47 48 49 50 51 52 53
  """
    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
54

55 56 57 58 59 60
  security.declareProtected(Permissions.AccessContentsInformation,
                            'getLastGeneratedId')
  def getLastGeneratedId(self,id_group=None,default=None):
    """
    Get the last id generated
    """
61
    if getattr(aq_base(self), 'dict_ids', None) is None:
62 63 64 65 66
      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
67
        
Jérome Perrin's avatar
Jérome Perrin committed
68
  security.declareProtected(Permissions.ModifyPortalContent,
69
                            'setLastGeneratedId')
70
  def setLastGeneratedId(self,new_id,id_group=None):
71 72 73 74
    """
    Set a new last id. This is usefull in order to reset
    a sequence of ids.
    """
75
    if getattr(aq_base(self), 'dict_ids', None) is None:
76 77
      self.dict_ids = PersistentMapping()
    if id_group is not None and id_group!='None':
78
      self.dict_ids[id_group] = new_id
79 80 81
        
  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewId')
Vincent Pelletier's avatar
Vincent Pelletier committed
82 83
  def generateNewId(self, id_group=None, default=None, method=None):
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
84
      Generate a new Id
Vincent Pelletier's avatar
Vincent Pelletier committed
85
    """
86 87 88 89

    dict_ids = getattr(aq_base(self), 'dict_ids', None)
    if dict_ids is None:
      dict_ids = self.dict_ids = PersistentMapping()
Vincent Pelletier's avatar
Vincent Pelletier committed
90 91

    new_id = None
92
    if id_group is not None and id_group != 'None':
Vincent Pelletier's avatar
Vincent Pelletier committed
93
      # Getting the last id
94 95 96 97 98
      if default is None:
        default = 0
      last_id = dict_ids.get(id_group, default)
      if method is None:
        new_id = new_id + 1
99
      else:
100
        new_id = method(new_id)  
101
      # Store the new value
102
      dict_ids[id_group] = new_id
Vincent Pelletier's avatar
Vincent Pelletier committed
103
    return new_id
Jean-Paul Smets's avatar
Jean-Paul Smets committed
104

105
  security.declareProtected(Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
106
                            'getDictLengthIdsItems')
107 108 109 110 111 112 113 114 115 116
  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()

117 118 119 120 121 122 123
  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')
124
    dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None)
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
    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))

143
  security.declareProtected(Permissions.AccessContentsInformation,
Aurel's avatar
Aurel committed
144
                            'getLastLengthGeneratedId')
Aurel's avatar
Aurel committed
145
  def getLastLengthGeneratedId(self, id_group, default=None):
146 147 148 149
    """
    Get the last length id generated
    """
    # check in persistent mapping if exists
150
    if getattr(aq_base(self), 'dict_length_ids', None) is not None:
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
      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

166
  security.declareProtected(Permissions.AccessContentsInformation,
167
                            'generateNewLengthIdList')
168
  def generateNewLengthIdList(self, id_group=None, id_count=1, default=None,
169
                              store=True):
170
    """
171 172
      Generates a list of Ids.
      The ids are generated using mysql and then stored in a Length object in a
173
      persistant mapping to be persistent.
174
      We use MySQL to generate IDs, because it is atomic and we don't want
175
      to generate any conflict at zope level. The possible downfall is that
176 177 178
      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.
179

180
      store : if we want to store the new id into the zodb, we want it
181
              by default
182 183
    """
    new_id = None
184 185 186 187
    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)
188
    if default is None:
189 190 191 192 193 194 195 196 197 198 199
      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.'
200 201 202 203
    try:
      result = query(id_group=id_group, id_count=id_count, default=default)
    finally:
      commit()
204
    new_id = result[0]['LAST_INSERT_ID()']
205
    if store:
206
      if getattr(aq_base(self), 'dict_length_ids', None) is None:
207 208 209 210 211
        # 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)
212
      self.dict_length_ids[id_group].set(new_id)
213 214 215 216
    return range(new_id - id_count, new_id)

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewLengthId')
217
  def generateNewLengthId(self, id_group=None, default=None, store=1):
218 219 220 221
    """
      Generates an Id.
      See generateNewLengthIdList documentation for details.
    """
222 223
    return self.generateNewLengthIdList(id_group=id_group, id_count=1, 
        default=default, store=store)[0]
224

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