[Checkins] SVN: z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/ * Add tests for when there are more then one shared lock.

Michael Kerrin michael.kerrin at openapp.ie
Tue Jun 19 14:34:39 EDT 2007


Log message for revision 76803:
  * Add tests for when there are more then one shared lock.
  
  * Add event handler to indirectly lock objects that are added to an locked
    collection.
  

Changed:
  U   z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/configure.zcml
  U   z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/indirecttokens.py
  U   z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/manager.py
  U   z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/tests.py

-=-
Modified: z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/configure.zcml
===================================================================
--- z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/configure.zcml	2007-06-19 18:18:57 UTC (rev 76802)
+++ z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/configure.zcml	2007-06-19 18:34:39 UTC (rev 76803)
@@ -16,7 +16,7 @@
   <class class=".manager.DAVLockmanager">
     <require
        permission="zope.View"
-       attributes="islocked islockable"
+       attributes="islockable"
        />
 
     <require
@@ -45,4 +45,10 @@
      handler=".indirecttokens.removeEndedTokens"
      />
 
+  <subscriber
+     for="zope.interface.Interface
+          zope.app.container.interfaces.IObjectAddedEvent"
+     handler=".manager.indirectlyLockObjectOnAdd"
+     />
+
 </configure>

Modified: z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/indirecttokens.py
===================================================================
--- z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/indirecttokens.py	2007-06-19 18:18:57 UTC (rev 76802)
+++ z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/indirecttokens.py	2007-06-19 18:34:39 UTC (rev 76803)
@@ -445,18 +445,3 @@
         # token has ended so it should be removed via the register method
         roottoken.utility.register(token)
         del index[key_ref]
-
-
-# Can we restrict this further by saying only for certain types of objects.
- at zope.component.adapter(
-    zope.interface.Interface, zope.app.container.interfaces.IObjectAddedEvent)
-def checkObjectAdded(object, event):
-    parent = object.__parent__
-    utility = zope.component.queryUtility(
-        zope.locking.interfaces.ITokenUtility, context = parent)
-    if utility:
-        token = utility.get(parent)
-        if token is not None:
-            if interfaces.IIndirectToken.providedBy(token):
-                token = token.roottoken
-            utility.register(IndirectToken(object, token))

Modified: z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/manager.py
===================================================================
--- z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/manager.py	2007-06-19 18:18:57 UTC (rev 76802)
+++ z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/manager.py	2007-06-19 18:34:39 UTC (rev 76803)
@@ -20,9 +20,11 @@
 import zope.component
 import zope.interface
 import zope.security.management
+import zope.publisher.interfaces.http
 from zope.app.container.interfaces import IReadContainer
 import z3c.dav.interfaces
 import z3c.dav.locking
+import z3c.dav.ifvalidator
 
 import interfaces
 import indirecttokens
@@ -33,11 +35,20 @@
 class DAVLockmanager(object):
     """
 
+      >>> import UserDict
       >>> from zope.interface.verify import verifyObject
       >>> from zope.locking import utility, utils
       >>> from zope.locking.adapters import TokenBroker
       >>> from zope.traversing.browser.absoluteurl import absoluteURL
+      >>> from zope.app.container.contained import ObjectAddedEvent
 
+      >>> class ReqAnnotation(UserDict.IterableUserDict):
+      ...    zope.interface.implements(zope.annotation.interfaces.IAnnotations)
+      ...    def __init__(self, request):
+      ...        self.data = request._environ.setdefault('annotation', {})
+      >>> zope.component.getGlobalSiteManager().registerAdapter(
+      ...    ReqAnnotation, (zope.publisher.interfaces.http.IHTTPRequest,))
+
       >>> file = Demo()
 
     Before we register a ITokenUtility utility make sure that the DAVLockmanager
@@ -74,7 +85,8 @@
       >>> adapter.islockable()
       True
 
-    Lock with an exclusive lock token.
+    Exclusive lock token
+    --------------------
 
       >>> locktoken = adapter.lock(u'exclusive', u'write',
       ...    u'Michael', datetime.timedelta(seconds = 3600), '0')
@@ -128,7 +140,8 @@
       ...
       ConflictError: The context is not locked, so we can't unlock it.
 
-    Shared locking support.
+    Shared locking
+    --------------
 
       >>> locktoken = adapter.lock(u'shared', u'write', u'Michael',
       ...    datetime.timedelta(seconds = 3600), '0')
@@ -141,15 +154,113 @@
       >>> activelock.locktoken #doctest:+ELLIPSIS
       ['opaquelocktoken:...
 
-      >>> util.get(file) == activelock.token
+      >>> sharedlocktoken = util.get(file)
+      >>> sharedlocktoken == activelock.token
       True
       >>> zope.locking.interfaces.ISharedLock.providedBy(activelock.token)
       True
 
+    Make sure that the meta-data on the lock token is correct.
+
+      >>> len(sharedlocktoken.annotations[WEBDAV_LOCK_KEY])
+      2
+      >>> sharedlocktoken.annotations[WEBDAV_LOCK_KEY]['principal_ids']
+      ['michael']
+      >>> len(sharedlocktoken.annotations[WEBDAV_LOCK_KEY][locktoken])
+      2
+      >>> sharedlocktoken.annotations[WEBDAV_LOCK_KEY][locktoken]['owner']
+      u'Michael'
+      >>> sharedlocktoken.annotations[WEBDAV_LOCK_KEY][locktoken]['depth']
+      '0'
+      >>> sharedlocktoken.duration
+      datetime.timedelta(0, 3600)
+      >>> sharedlocktoken.principal_ids
+      frozenset(['michael'])
+
+    We can have multiple WebDAV shared locks on a resource. We implement
+    this by storing the data for the second lock token in the annotations.
+
+      >>> locktoken2 = adapter.lock(u'shared', u'write', u'Michael 2',
+      ...    datetime.timedelta(seconds = 1800), '0')
+      >>> len(sharedlocktoken.annotations[WEBDAV_LOCK_KEY])
+      3
+
+    We need to keep track of the principal associated with the lock token
+    our selves as we can only create one shared lock token with zope.locking,
+    and after removing the first shared lock the zope.locking token will
+    be removed.
+
+      >>> sharedlocktoken.annotations[WEBDAV_LOCK_KEY]['principal_ids']
+      ['michael', 'michael']
+      >>> sharedlocktoken.annotations[WEBDAV_LOCK_KEY][locktoken2]['owner']
+      u'Michael 2'
+      >>> sharedlocktoken.annotations[WEBDAV_LOCK_KEY][locktoken2]['depth']
+      '0'
+
+    Note that the timeout is shared across the two WebDAV tokens. After
+    creating the second shared lock the duration changed to the last lock
+    taken out. We need to do this in order to make sure that the token is
+    ended at some point. What we probable want to do here is to take the
+    largest remaining duration so that a lock token doesn't expire earlier
+    then expected but might last slightly longer then expected for some one.
+
+      >>> sharedlocktoken.duration
+      datetime.timedelta(0, 1800)
+
+    After unlocking the first first locktoken the information for this token
+    is removed.
+
       >>> adapter.unlock(locktoken)
+      >>> util.get(file) == sharedlocktoken
+      True
+      >>> len(sharedlocktoken.annotations[WEBDAV_LOCK_KEY])
+      2
+      >>> sharedlocktoken.annotations[WEBDAV_LOCK_KEY]['principal_ids']
+      ['michael']
+      >>> locktoken in sharedlocktoken.annotations[WEBDAV_LOCK_KEY]
+      False
+      >>> locktoken2 in sharedlocktoken.annotations[WEBDAV_LOCK_KEY]
+      True
 
-    Recursive lock suport.
+      >>> adapter.unlock(locktoken2)
 
+      >>> util.get(file) is None
+      True
+
+    If an exclusive lock already exists on a file from an other application
+    then this should fail with an already locked response.
+
+      >>> exclusivelock = util.register(
+      ...    zope.locking.tokens.ExclusiveLock(file, 'michael'))
+      >>> adapter.lock(u'shared', u'write', u'Michael2',
+      ...    datetime.timedelta(seconds = 3600), '0') #doctest:+ELLIPSIS
+      Traceback (most recent call last):
+      ...
+      AlreadyLocked: <z3c.davapp.zopelocking.tests.Demo object at ...>: None
+      >>> exclusivelock.end()
+
+    If a shared lock is taken out on the resource, then this lock token is
+    probable not annotated with the extra information required by webdav.
+
+      >>> sharedlock = util.register(
+      ...    zope.locking.tokens.SharedLock(file, ('michael',)))
+      >>> len(sharedlock.annotations)
+      0
+      >>> locktoken = adapter.lock(u'shared', u'write', u'Michael 2',
+      ...    datetime.timedelta(seconds = 3600), '0')
+      >>> len(sharedlock.annotations[WEBDAV_LOCK_KEY])
+      2
+      >>> sharedlock.annotations[WEBDAV_LOCK_KEY]['principal_ids']
+      ['michael']
+      >>> locktoken in sharedlock.annotations[WEBDAV_LOCK_KEY]
+      True
+      >>> sharedlock.principal_ids
+      frozenset(['michael'])
+      >>> sharedlock.end()
+
+    Recursive lock suport
+    ---------------------
+
       >>> demofolder = DemoFolder()
       >>> demofolder['demo'] = file
 
@@ -171,8 +282,26 @@
       >>> activelock.lockscope
       [u'exclusive']
 
-    Already locked support.
+    If a collection is locked with an infinite depth lock then all member
+    resources are indirectly locked. Any resource that is added to this
+    collection then becomes indirectly locked against the lockroot for
+    the collection.
 
+      >>> file2 = Demo()
+      >>> demofolder['file2'] = file2
+      >>> indirectlyLockObjectOnAdd(file2,
+      ...    ObjectAddedEvent(file2, demofolder, 'file2'))
+      >>> file2lock = util.get(file2)
+      >>> interfaces.IIndirectToken.providedBy(file2lock)
+      True
+      >>> file2lock.roottoken == util.get(demofolder)
+      True
+
+    Already locked
+    --------------
+
+    Adapter is now defined on the demofolder.
+
       >>> adapter.lock(u'exclusive', u'write', u'Michael',
       ...    datetime.timedelta(seconds = 100), 'infinity') #doctest:+ELLIPSIS
       Traceback (most recent call last):
@@ -180,14 +309,20 @@
       AlreadyLocked...
       >>> adapter.islocked()
       True
-      >>> activelock = adapter.getActivelock(locktoken)
-      >>> len(activelock.locktoken)
-      1
 
       >>> adapter.unlock(locktoken[0])
 
-    Some error conditions.
+      >>> locktoken = DAVLockmanager(file).lock(u'shared', u'write',
+      ...    u'Michael', datetime.timedelta(seconds = 3600), '0')
+      >>> adapter.lock(u'shared', u'write', u'Michael 2',
+      ...    datetime.timedelta(seconds = 3600), 'infinity') #doctest:+ELLIPSIS
+      Traceback (most recent call last):
+      ...
+      AlreadyLocked:...
 
+    Some error conditions
+    ---------------------
+
       >>> adapter.lock(u'notexclusive', u'write', u'Michael',
       ...    datetime.timedelta(seconds = 100), 'infinity') # doctest:+ELLIPSIS
       Traceback (most recent call last):
@@ -195,7 +330,11 @@
       UnprocessableError: ...
 
     Cleanup
+    -------
 
+      >>> zope.component.getGlobalSiteManager().unregisterAdapter(
+      ...    ReqAnnotation, (zope.publisher.interfaces.http.IHTTPRequest,))
+      True
       >>> zope.component.getGlobalSiteManager().unregisterUtility(
       ...    util, zope.locking.interfaces.ITokenUtility)
       True
@@ -264,7 +403,17 @@
                 annots = roottoken.annotations[WEBDAV_LOCK_KEY] = OOBTree()
                 annots["principal_ids"] = [principal_id]
             else:
+                if not zope.locking.interfaces.ISharedLock.providedBy(roottoken):
+                    # We need to some how figure out how to add preconditions
+                    # code to the exceptions. As a 'no-conflicting-lock'
+                    # precondition code is valid here. 
+                    raise z3c.dav.interfaces.AlreadyLocked(
+                        self.context,
+                        message = u"""A conflicting lock already exists for
+                                      this resource""")
+
                 roottoken.add((principal_id,))
+                roottoken.duration = duration
                 if WEBDAV_LOCK_KEY not in roottoken.annotations:
                     # Locked by an alternative application
                     annots = roottoken.annotations[WEBDAV_LOCK_KEY] = OOBTree()
@@ -338,3 +487,28 @@
     principal_id = principal_ids[0]
 
     return principal_id
+
+
+ at zope.component.adapter(
+    zope.interface.Interface, zope.app.container.interfaces.IObjectAddedEvent)
+def indirectlyLockObjectOnAdd(object, event):
+    interaction = zope.security.management.queryInteraction()
+
+    if interaction:
+        request = interaction.participations[0]
+        if zope.publisher.interfaces.http.IHTTPRequest.providedBy(request) \
+               and z3c.dav.ifvalidator.matchesIfHeader(object, request):
+            parent = object.__parent__
+            utility = zope.component.queryUtility(
+                zope.locking.interfaces.ITokenUtility, context = parent)
+            # XXX - potentially two problems here. One the object is already
+            # locked then the if we have created a conflicting lock. Two the
+            # lock on the collection might not be an infinite lock and as such
+            # we should not lock the object.
+            if utility and utility.get(object) is None:
+                token = utility.get(parent)
+                if token is not None:
+                    if interfaces.IIndirectToken.providedBy(token):
+                        token = token.roottoken
+                    utility.register(
+                        indirecttokens.IndirectToken(object, token))

Modified: z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/tests.py
===================================================================
--- z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/tests.py	2007-06-19 18:18:57 UTC (rev 76802)
+++ z3c.davapp.zopelocking/trunk/src/z3c/davapp/zopelocking/tests.py	2007-06-19 18:34:39 UTC (rev 76803)
@@ -19,7 +19,8 @@
 import zope.component
 import zope.interface
 from zope.testing import doctest
-from zope.security.testing import Principal, Participation
+from zope.security.testing import Principal
+from zope.publisher.browser import TestRequest
 from zope.security.management import newInteraction, endInteraction, \
      queryInteraction
 import zope.event
@@ -115,7 +116,8 @@
     z3c.etree.testing.etreeSetup(test)
 
     # create principal
-    participation = Participation(Principal('michael'))
+    participation = TestRequest()
+    participation.setPrincipal(Principal('michael'))
     if queryInteraction() is not None:
         queryInteraction().add(participation)
     else:



More information about the Checkins mailing list