ObjectStoreCoordinator.py :  » Database » Modeling-Framework » Modeling-0.9 » Modeling » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » Modeling Framework 
Modeling Framework » Modeling 0.9 » Modeling » ObjectStoreCoordinator.py
# -*- coding: iso-8859-1 -*-
#-----------------------------------------------------------------------------
# Modeling Framework: an Object-Relational Bridge for python
#
# Copyright (c) 2001-2004 Sbastien Bigaret <sbigaret@users.sourceforge.net>
# All rights reserved.
#
# This file is part of the Modeling Framework.
#
# This code is distributed under a "3-clause BSD"-style license;
# see the LICENSE file for details.
#-----------------------------------------------------------------------------


"""
ObjectStoreCoordinator

  __TBD


    Notifications

      The following notifications are defined and posted:
      
        - 'CooperatingObjectStoreWasAddedNotification'
          object: the 'ObjectStoreCoordinator' instance
  
        - 'CooperatingObjectStoreWasRemovedNotification'
          object: the removed 'ObjectStoreCoordinator' instance
  
        - 'CooperatingObjectStoreNeededNotification'
          object: the ObjectStoreCoordinator posting the request
          userInfo: either a GlobalID, an object ('DatabaseObject') or
                    a FetchSpecification. This object, passed along with the
                    notification, is assumed to be able to respond to the
                    message 'entityName()'
      
  CVS information

    $Id: ObjectStoreCoordinator.py 966 2006-02-25 13:08:08Z sbigaret $

"""

__version__='$Revision: 966 $'[11:-2]

from logging import error,warn,debug
import sys, traceback, StringIO

from NotificationFramework import NotificationCenter
NC=NotificationCenter

from ObjectStore import ObjectStore

# Modules constants: Notifications

CooperatingObjectStoreWasAddedNotification='CooperatingObjectStoreWasAddedNotification'
CooperatingObjectStoreWasRemovedNotification='CooperatingObjectStoreWasRemovedNotification'
CooperatingObjectStoreNeededNotification='CooperatingObjectStoreNeededNotification'

## The lock is used for module's methods AND class methods
## Remember that you should *NOT* directly access to module's variable
## '__defaultCoordinator' since it is considered private (MT-safety)
from threading import RLock
__defaultCoordinator=None
defaultCoordinator_lock=RLock()
lock=defaultCoordinator_lock.acquire
unlock=defaultCoordinator_lock.release

def defaultCoordinator():
  """
  Returns the default coordinator used in the application.
  If the coordinator has not been set before this function is called, a
  default one is created and returned
  """
  lock()
  try:
    global __defaultCoordinator
    if __defaultCoordinator is None:
      __defaultCoordinator=ObjectStoreCoordinator()
    return __defaultCoordinator
  finally:
    unlock()
    
def setDefaultCoordinator(aCoordinator):
  """
  Sets the default coordinator
  """
  lock()
  try:
    __defaultCoordinator=aCoordinator
  finally:
    unlock()

## ObjectStoreCoordinator
class ObjectStoreCoordinator(ObjectStore):
  """
  ObjectStoreCoordinator API

    Design Pattern: Chain of repsonsability

    __TBD

    By default only one 'ObjectStoreCoordinator' is created for an entire
    application ; however multiple coordinators might exist in an application
    (reasons will be details ine the future __TBD)

    Implementation of interface 'ObjectStore'

      With respect to implemented interface 'ObjectStoreInterface', the
      following methods make sense and are consequently overriden:
      
        - arrayFaultWithSourceGlobalID
  
        - faultForGlobalID
  
        - invalidateAllObjects
  
        - invalidateObjectsWithGlobalIDs
  
        - objectsForSourceGlobalID
  
        - objectsWithFetchSpecification
  
        - refaultObject
  
        - savesChangesInEditingContext
  
      These methods' implementations simply forwards to the dedicated
      coordinator(s) the message ; determination of the coordinator(s)
      involved is done using either 'objectStoreForFetchSpecification',
      'objectStoreForGlobalID' or 'objectStoreForObject'.

    Implementation notes:
    
      Remember that you should *NOT* access directly to instance variables,
      they are considered private (MT-safety)
  """

  def __init__(self):
    """
    """
    self.__instanceLock=RLock()
    self.lock()
    try:
      self._cooperatingObjectStores=[]
    finally: 
      self.unlock()

  def addCooperatingObjectStore(self, aStore):
    """
    Adds a cooperating object store

    Posts CooperatingObjectStoreWasAdded

    See also: removeCooperatingObjectStore
    """
    self.lock()
    try:
      if aStore not in self._cooperatingObjectStores:
        self._cooperatingObjectStores.append(aStore)
        NC.postNotification(CooperatingObjectStoreWasAddedNotification, aStore)
    finally:
      self.unlock()

  def cooperatingObjectStores(self):
    """
    Returns a copy of the list of registered cooperatingObjectStores
    """
    self.lock()
    try:
      return tuple(self._cooperatingObjectStores)
    finally:
      self.unlock()

  def forwardUpdateForObject(self, anObject, aDictionaryOfChanges):
    """
    
    Called by a CooperatingObjectStore (usually a DatabaseContext) when it
    needs to notify that some changes were noticed that are not necessarily
    already noticed --this is the case, for example, when a toMany
    relationship, with no inverseRelationship declared in the model, has been
    changed.

    The ObjectStoreCoordinator simply locates the appropriate
    CooperatingObjectStore for 'anObject' then forwards the notification of
    changes to it.

    Refer to DatabaseContext.recordUpdateForObject() and DatabaseOperation for
    a more complete discussion on this topic.

    Paremeters:
    
      anObject -- the object that needs to be notified of some changes.

      aDictionary -- dictionary key/values corresponding to changes

    See also: objectStoreForObject(),
              CooperatingObjectStore.recordUpdateForObject()
    """
    self.lock()
    try:
      store=self.objectStoreForObject(anObject)
      store.recordUpdateForObject(anObject, aDictionaryOfChanges)
    finally:
      self.unlock()

  def _objectStoreForFetchSpecification(self, aFetchSpecification):
    """
    Private method returning the 1st cooperating store answering positively
    to message 'handlesFetchSpecification'.
    Called by 'objectStoreForFetchSpecification'.
    """
    self.lock()
    try:
      for store in self._cooperatingObjectStores:
        if store.handlesFetchSpecification(aFetchSpecification):
          return store
      return None
    finally:
      self.unlock()

  def objectStoreForFetchSpecification(self, aFetchSpecification):
    """
    Iterates on objectStores and returns the first one who answers positively
    to message 'handlesFetchSpecification'.

    Posts 'CooperatingObjectStoreNeeded' in case no store can be found at
    first glance, so that listeners get a chance to register an adequate
    'CooperatingObjectStore' ; then the requested 'ObjectStore' is returned,
    or 'None' if it still cannot be found.
    """
    self.lock()
    try:
      store=self._objectStoreForFetchSpecification(aFetchSpecification)
      if store is not None:
        return store
      # We need one, let the observers have a chance to register it
      NC.postNotification(CooperatingObjectStoreNeededNotification,
                          self,
                          {'fetchSpecification': aFetchSpecification})
      return self._objectStoreForFetchSpecification(aFetchSpecification)
    finally:
      self.unlock()

  def _objectStoreForEntityName(self, entityName):
    """
    Private method returning the 1st cooperating store answering positively
    to message `ownsEntityName`. Called by `objectStoreForEntityName`.

    Note: This method does not lock self. Do not call this by hand in a
    multi-threaded env.
    
    """
    for store in self._cooperatingObjectStores:
      if store.ownsEntityName(entityName):
        return store
    return None

  def objectStoreForEntityName(self, entityName):
    """
    Iterates on objectStores and returns the first one who answers positively
    to message 'ownsEntityName'

    Posts 'CooperatingObjectStoreNeeded' in case no store can be found at
    first glance, so that listeners get a chance to register an adequate
    'CooperatingObjectStore' ; then the requested 'ObjectStore' is returned,
    or 'None' if it still cannot be found.
    """
    self.lock()
    try:
      store=self._objectStoreForEntityName(entityName)
      if store is not None:
        return store
      # We need one, let the observers have a chance to register it
      NC.postNotification(CooperatingObjectStoreNeededNotification,
                          self,
                          {'entityName': entityName})
      return self._objectStoreForEntityName(entityName)
    finally:
      self.unlock()

  def _objectStoreForGlobalID(self, aGlobalID):
    """
    Private method returning the 1st cooperating store answering positively
    to message 'ownsGlobalID'. Called by 'objectStoreForGlobalID'.

    Note: This method does not lock self. Do not call tyis by hand in a
    mutli-threaded env.
    
    """
    for store in self._cooperatingObjectStores:
      if store.ownsGlobalID(aGlobalID):
        return store
    return None

  def objectStoreForGlobalID(self, aGlobalID):
    """
    Iterates on objectStores and returns the first one who answers positively
    to message 'ownsGlobalID'

    Posts 'CooperatingObjectStoreNeeded' in case no store can be found at
    first glance, so that listeners get a chance to register an adequate
    'CooperatingObjectStore' ; then the requested 'ObjectStore' is returned,
    or 'None' if it still cannot be found.
    """
    self.lock()
    try:
      store=self._objectStoreForGlobalID(aGlobalID)
      if store is not None:
        return store
      # We need one, let the observers have a chance to register it
      NC.postNotification(CooperatingObjectStoreNeededNotification,
                          self,
                          {'globalID': aGlobalID})
      return self._objectStoreForGlobalID(aGlobalID)
    finally:
      self.unlock()

  def _objectStoreForObject(self, anObject):
    """
    Private method returning the 1st cooperating store answering positively
    to message 'ownsObject'. Called by 'objectStoreForObject'.
    """
    self.lock()
    try:
      for store in self._cooperatingObjectStores:
        if store.ownsObject(anObject):
          return store
      return None
    finally:
      self.unlock()

  def objectStoreForObject(self, anObject):
    """
    Iterates on objectStores and returns the first one who answers positively
    to message 'ownsObject'
    
    Posts 'CooperatingObjectStoreNeeded' in case no store can be found at
    first glance, so that listeners get a chance to register an adequate
    'CooperatingObjectStore' ; then the requested 'ObjectStore' is returned,
    or 'None' if it still cannot be found.

    """
    self.lock()
    try:
      store=self._objectStoreForObject(anObject)
      if store is not None:
        return store
      # We need one, let the observers have a chance to register it
      NC.postNotification(CooperatingObjectStoreNeededNotification,
                          self,
                          {'object': anObject})
      return self._objectStoreForObject(anObject)
    finally:
      self.unlock()

  def removeCooperatingObjectStore(self, aStore):
    """
    Removes a cooperating object store

    Posts CooperatingObjectStoreWasRemoved

    See also: addCooperatingObjectStore
    """
    self.lock()
    try:
      self._cooperatingObjectStores.remove(aStore)
      NC.postNotification(CooperatingObjectStoreWasRemovedNotification,
                          aStore)
    finally:
      self.unlock()

  ## ObjectStore API
  ## Simply forwards the message to the appropriate cooperating store(s)  
  def arrayFaultWithSourceGlobalID(self, aGlobalID, aRelationshipName,
                                   anEditingContext):
    "See ObjectStore for details"
    self.lock()
    try:
      store=self.objectStoreForGlobalID(aGlobalID)
      return store.arrayFaultWithSourceGlobalID(aGlobalID, aRelationshipName,
                                                anEditingContext)
    finally:
      self.unlock()
    
  def faultForGlobalID(self, aGlobalID, anEditingContext):
    """
    Forwards the message to the adequate CooperatingObjectStore, usually a
    DatabaseContext, and returns the result.
    """
    self.lock()
    try:
      store=self.objectStoreForGlobalID(aGlobalID)
      return store.faultForGlobalID(aGlobalID, anEditingContext)
    finally:
      self.unlock()
  
  def faultForRawRow(self, row, entityName, anEditingContext):
    """
    Forwards the message to the adequate CooperatingObjectStore, usually a
    DatabaseContext, and returns the result.
    """
    self.lock()
    try:
      from Modeling.FetchSpecification import FetchSpecification
      fs=FetchSpecification(entityName)
      store=self.objectStoreForFetchSpecification(fs)
      return store.faultForRawRow(row, entityName, anEditingContext)
    finally:
      self.unlock()
  
  def initializeObject(self, anObject, aGlobalID, anEditingContext):
    "See ObjectStore for details"
    self.lock()
    try:
      objectStore=self.objectStoreForGlobalID(aGlobalID)
      objectStore.initializeObject(anObject, aGlobalID, anEditingContext)
    finally:
      self.unlock()
    
  def invalidateAllObjects(self):
    "See ObjectStore for details"
    self.lock()
    try:
      for store in self._cooperatingObjectStores:
        store.invalidateAllObjects()
    finally:
      self.unlock()

  def invalidateObjectsWithGlobalIDs(self, globalIDs):
    "See ObjectStore"
    self.lock()
    try:
      for globalID in globalIDs:
        store=self.objectStoreForGlobalID(globalID)
        store.invalidateObjectsWithGlobalIDs((globalID,))
    finally:
      self.unlock()

  def objectsForSourceGlobalID(self, aGlobalID, aRelationshipName,
                               anEditingContext):
    "See ObjectStore for details"
    lock()
    try:
      store=self.objectStoreForGlobalID(aGlobalID)
      store.objectsForSourceGlobalID(aGlobalID, aRelationshipName,
                                     anEditingContext)
    finally:
      self.unlock()

  def objectsWithFetchSpecification(self, aFetchSpecification,
                                    anEditingContext):
    "See ObjectStore for details"
    self.lock()
    try:
      store=self.objectStoreForFetchSpecification(aFetchSpecification)
      if store is None:
        raise RuntimeError, \
              'Unable to find the ObjectStore for FetchSpecification with '\
              'entityName: %s'%aFetchSpecification.entityName()
      return store.objectsWithFetchSpecification(aFetchSpecification, anEditingContext)
    finally:
      self.unlock()

  def objectsCountWithFetchSpecification(self, aFetchSpecification,
                                         anEditingContext):
    """
    Forwards the message to the appropriate CooperatingObjectStore, usually a
    DatabaseContext.
    
    Returns the approximate number of objects that would be returned by
    objectsWithFetchSpecification() if called with the very same parameters.
    
    About ``approximate'': the number returned is in fact the upper bound ; as
    the objects are not actually fetched against the database, it is not
    possible to determine whether some have already been deleted within
    anEditingContext (in which case objectsWithFetchSpecification() would not
    return them --see its documentation as well).

    Parameters:

      aFetchSpecification -- a FetchSpecification object describing the
        objects to be fetched

      anEditingContext -- the EditingContext in which the objects would be
        fetched. This is in fact an optional argument: since no objects are
        fetched it is not really needed.

    """
    self.lock()
    try:
      store=self.objectStoreForFetchSpecification(aFetchSpecification)
      if store is None:
        raise RuntimeError, \
              'Unable to find the ObjectStore for FetchSpecification with '\
              'entityName: %s'%aFetchSpecification.entityName()
      return store.objectsCountWithFetchSpecification(aFetchSpecification, anEditingContext)
    finally:
      self.unlock()

  def ownsObject(self, anObject):
    """
    Tells whether the ObjectStore can handle the supplied
    object. ObjectStoreCoordinator determines this by searching a matching
    CooperatingObjectStore for that object. If such a CooperatingObjectStore
    is found, returns true (1), else return false (0).

    Note: this method directly triggers objectStoreForObject() --refer to its
    doc. for more info. on its side-effect.
    
    See also: objectStoreForObject()
    """
    try:
      coopObjStore=self.objectStoreForObject(anObject)
    except:
      #if 1: # __TBD if DEBUG (somehow)
      #  exc=StringIO.StringIO()
      #  traceback.print_exc(file=exc)
      #  debug(exc.getvalue())
      #  del exc
      return 0
    if coopObjStore is not None: return 1
    return 0
  
  def refaultObject(self, anObject, aGlobalID, anEditingContext):
    "See ObjectStore for details"
    self.lock()
    try:
      store=self.objectStoreForObject(anObject)
      store.refaultObject(anObject, aGlobalID, anEditingContext)
    finally:
      self.unlock()
      
  def rootObjectStore(self):
    """
    Returns 'self', since an 'ObjectStoreCoordinator' object is designed to
    be the root of an ObjectStore hierarchy.

    See also: interface 'ObjectStore' for details
    """
    self.lock()
    try:
      return self
    finally:
      self.unlock()
      
  def saveChangesInEditingContext(self, anEditingContext):
    "See ObjectStore for details"
    anEditingContext.lock()
    self.lock()
    # take a lock on the Coop.Obj.Store's database
    for coopObjStore in self._cooperatingObjectStores:
      coopObjStore.database().lock()
    try: # global try/finally block
      _error=""
      try:
        for coopObjStore in self._cooperatingObjectStores:
          coopObjStore.prepareForSaveWithCoordinator(self,
                                                     anEditingContext)
      except:
        warn("Got an exception before performChanges")
        _error="prepareForSaveWithCoordinator() failed on %s"%repr(coopObjStore)
        raise
      try:
        for coopObjStore in self._cooperatingObjectStores:
          coopObjStore.recordChangesInEditingContext()
      except:
        warn("Got an exception during recordChangesInEditingContext()")
        _error="recordChangesInEditingContext() failed on %s"%repr(coopObjStore)
        raise

      try:
        for coopObjStore in self._cooperatingObjectStores:
          coopObjStore.performChanges()
      except:
        warn("Got an exception: rollback changes")
        if 1: # __TBD if DEBUG (somehow)
          exc=StringIO.StringIO()
          traceback.print_exc(file=exc)
          warn(exc.getvalue())
          del exc
        exctype, value = sys.exc_info()[:2]
        
        _error="performChanges() failed on %s: %s:%s"%(repr(coopObjStore),
                                                       exctype, value)
        for coopObjStore in self._cooperatingObjectStores:
          try:
            coopObjStore.rollbackChanges()
          except:
            exctype, value = sys.exc_info()[:2]
            warn("got an exception for rollback() on %s"%str(coopObjStore))
            _error+="\nrollbackChanges() failed on %s: %s:%s"%(repr(coopObjStore),exctype,value)
            if 1: # __TBD if DEBUG (somehow)
              exc=StringIO.StringIO()
              traceback.print_exc(file=exc)
              debug(exc.getvalue())
              del exc

        raise RuntimeError, _error
      
      _commitDidFail=0
      for coopObjStore in self._cooperatingObjectStores:
        try:
          coopObjStore.commitChanges()
        except:
          _commitDidFail=1
          if 1: # __TBD if DEBUG (somehow)
            exc=StringIO.StringIO()
            traceback.print_exc(file=exc)
            warn(exc.getvalue())
            del exc

          break
        
      if _commitDidFail: # One of the commit failed: try to revert everything
        warn("Got an exception while committing: trying to rollback changes")
        for coopObjStore in self._cooperatingObjectStores:
          try:
            coopObjStore.rollbackChanges()
          except:
            warn("got an exception for rollback() on %s"%str(coopObjStore))
            if 1: # __TBD if DEBUG (somehow)
              exc=StringIO.StringIO()
              traceback.print_exc(file=exc)
              debug(exc.getvalue())
              del exc

        raise RuntimeError, "commitChanges() failed"

      else:
        # All commits did succeed: time to sync. Databases' snapshots
        # with their DBContext's
        for coopObjStore in self._cooperatingObjectStores:
          coopObjStore.finalizeCommitChanges()
        
    finally:
      for coopObjStore in self._cooperatingObjectStores:
        coopObjStore.database().unlock()
      self.unlock()
      anEditingContext.unlock()

  # Instance lock
  def lock(self):
    """
    Acquire the lock for the ObjectStoreCoordinator. Calls to 'lock()' should
    be balanced with the same number of 'unlock()' for the lock to be released.

    You normally do not need to call this method directly.

    See also: unlock()
    """
    self.__instanceLock.acquire()

  def unlock(self):
    """
    Releases the lock for the ObjectStoreCoordinator.

    See also: lock()
    """
    self.__instanceLock.release()

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.