IdTool.py 8.96 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
from Acquisition import aq_base
Jean-Paul Smets's avatar
Jean-Paul Smets committed
30 31
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass, DTMLFile, PersistentMapping
32
from Products.ERP5Type.Tool.BaseTool import BaseTool
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33
from Products.ERP5Type import Permissions
34
from Products.CMFCore.utils import getToolByName
Ivan Tyagov's avatar
Ivan Tyagov committed
35
from zLOG import LOG, WARNING
Jean-Paul Smets's avatar
Jean-Paul Smets committed
36 37
from Products.ERP5 import _dtmldir

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

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

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

    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
89 90

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

110
  security.declareProtected(Permissions.AccessContentsInformation,
Jérome Perrin's avatar
Jérome Perrin committed
111
                            'getDictLengthIdsItems')
112 113 114 115 116 117 118 119 120 121
  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()

122 123 124 125 126 127 128
  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')
129
    dict_length_ids = getattr(aq_base(self), 'dict_length_ids', None)
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
    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))

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

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

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

  security.declareProtected(Permissions.AccessContentsInformation,
                            'generateNewLengthId')
222
  def generateNewLengthId(self, id_group=None, default=None, store=1):
223 224 225 226
    """
      Generates an Id.
      See generateNewLengthIdList documentation for details.
    """
227 228
    return self.generateNewLengthIdList(id_group=id_group, id_count=1, 
        default=default, store=store)[0]
229

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