Local Site Manager
==================

We start from an empty folder. Since ``OFS.Folder``'s extend
``ObjectSiteManager`` they all get to be ``IPossibleSite``'s.

    >>> from OFS.Folder import Folder
    >>> site = Folder('site')

Of course we now need to transform that IPossibleSite into a real ISite.

    >>> import zope.component
    >>> from zope.app.component.hooks import setSite as setActiveSite
    >>> from zope.app.component.hooks import clearSite

    >>> from zope.app.component.hooks import setHooks
    >>> setHooks()

    >>> from five.localsitemanager import make_objectmanager_site
    >>> make_objectmanager_site(site)
    >>> sitemanager = site.getSiteManager()
    >>> sitemanager
    <PersistentComponents ...>

Make sure this site has it's ``__bases__`` configure appropriately.

    >>> sitemanager.__bases__
    (<BaseGlobalComponents ...>,)

Utilities
---------

Utilities can now be registered with our site manager. We can confirm this by
setting up a test utility.

    >>> from OFS.SimpleItem import SimpleItem
    >>> from zope import interface

    >>> class ITestUtility(interface.Interface): pass
    >>> class TestUtility(object):
    ...     interface.implements(ITestUtility)
    ...     def __init__(self, id):
    ...         self.id = id
    ...     def __repr__(self):
    ...         return '<Utility %s "%s">' % (self.__class__.__name__, self.id)

    >>> sitemanager.registerUtility(TestUtility('test'),
    ...                             name=u'hello_world',
    ...                             provided=ITestUtility)
    >>> sitemanager.getUtility(ITestUtility, name=u'hello_world')
    <Utility TestUtility "test">

Make sure the utility lookup only works when the correct active site has
been configured.

    >>> setActiveSite(None)
    >>> zope.component.queryUtility(ITestUtility, name=u'hello_world') is None
    True

    >>> setActiveSite(site)
    >>> zope.component.queryUtility(ITestUtility, name=u'hello_world')
    <Utility TestUtility "test">

Adapters
---------

Adapters can now be registered with our site manager. We can confirm this by
setting up a test adapter.

    >>> from OFS.SimpleItem import SimpleItem
    >>> from zope import interface

    >>> class IFoo(interface.Interface): pass
    >>> class Foo(object):
    ...     interface.implements(IFoo)
    ...     def __init__(self, id):
    ...         self.id = id

    >>> class ITestAdapter(interface.Interface): pass
    >>> class TestAdapter(object):
    ...     interface.implements(ITestAdapter)
    ...     def __init__(self, context):
    ...         self.context = context
    ...     def __repr__(self):
    ...         return '<Adapter %s adapting "%s">' % (self.__class__.__name__,
    ...                                                self.context.id)

    >>> sitemanager.registerAdapter(TestAdapter,
    ...                             required=(IFoo,),
    ...                             provided=ITestAdapter)
    >>> sitemanager.getAdapter(Foo('foo'), ITestAdapter)
    <Adapter TestAdapter adapting "foo">

Make sure the adapter lookup only works when the correct active site has
been configured.

    >>> setActiveSite(None)
    >>> zope.component.queryAdapter(Foo('foo'), ITestAdapter) is None
    True

    >>> setActiveSite(site)
    >>> zope.component.queryAdapter(Foo('foo'), ITestAdapter)
    <Adapter TestAdapter adapting "foo">
    >>> ITestAdapter(Foo('foo'))
    <Adapter TestAdapter adapting "foo">

Acquisition
-----------

Now to mix a little required Zope 2 confusion into everything, we must ensure
that the aq chain is predictable. 

Path relative to component registry
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

And based on consensus we decided that the
acquired parent of a returned utility should be the ``ISite`` that owns the
``ISiteManager`` that returned the utility. We need to ensure all the ways of
getting a utility have been covered. Of course this should only happen if the
utility is acquisition aware to begin with.

    >>> import Acquisition
    >>> from Acquisition.interfaces import IAcquirer

First off, our utility isn't aq-wrapped so asking it what is aq_parent is
should return None.

    >>> comp = sitemanager.queryUtility(ITestUtility, name=u'hello_world')
    >>> Acquisition.aq_parent(comp) is None
    True

So now we setup a utility that is aq-aware.

    >>> class AQTestUtility(Acquisition.Explicit, TestUtility): pass
    >>> sitemanager.registerUtility(AQTestUtility('test'),
    ...                             name=u'aq_wrapped',
    ...                             provided=ITestUtility)

And of course the aq parent should be the site now.

    >>> comp = sitemanager.getUtility(ITestUtility, name=u'aq_wrapped')
    >>> Acquisition.aq_parent(comp) is site
    True

And just to mix things up a bit. Getting back multiple utilities should allow
us to test both aq and non-aq based components.

We start with getUtilitiesFor():

    >>> utils = [x for x in sitemanager.getUtilitiesFor(ITestUtility)]
    >>> len(utils)
    2

    >>> nonaqutils = [(name, comp)
    ...               for name, comp in utils if not IAcquirer.providedBy(comp)]
    >>> len(nonaqutils)
    1
    >>> name, comp = nonaqutils[0]
    >>> Acquisition.aq_parent(comp) is None
    True

    >>> aqutils = [(name, comp)
    ...            for name, comp in utils if IAcquirer.providedBy(comp)]
    >>> len(aqutils)
    1
    >>> name, comp = aqutils[0]
    >>> Acquisition.aq_parent(comp) is site
    True

And then getAllUtilitiesRegisteredFor():

    >>> utils = [x for x in
    ...          sitemanager.getAllUtilitiesRegisteredFor(ITestUtility)]
    >>> len(utils)
    2

    >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)]
    >>> len(nonaqutils)
    1
    >>> comp = nonaqutils[0]
    >>> Acquisition.aq_parent(comp) is None
    True

    >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)]
    >>> len(aqutils)
    1
    >>> comp = aqutils[0]
    >>> Acquisition.aq_parent(comp) is site
    True

And registeredUtilities():

    >>> list(sitemanager.registeredUtilities())
    [UtilityRegistration(<PersistentComponents site>, ITestUtility, u'hello_world', <Utility TestUtility "test">, u''),
     UtilityRegistration(<PersistentComponents site>, ITestUtility, u'aq_wrapped', <Utility AQTestUtility "test">, u'')]
    >>> utils = [ r.component for r in sitemanager.registeredUtilities() ]
    >>> len(utils)
    2

    >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)]
    >>> len(nonaqutils)
    1
    >>> comp = nonaqutils[0]
    >>> Acquisition.aq_parent(comp) is None
    True

    >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)]
    >>> len(aqutils)
    1
    >>> comp = aqutils[0]
    >>> Acquisition.aq_parent(comp) is site
    True

Absolute Path
~~~~~~~~~~~~~

The approach to return utilites with a acquisition chain relative to
the component registry has the problem that it can return the wrong
physical path for the utility so computed URLs for the utility are
also wrong.

This can be fixed when the parent object of a utility is a local
component registry. But this is not like in Zope3 and has the problem
that utility registration is only visible below its registry.

Alternative solution: register the utility with its acquisition
context and it will be returned wrapped into its original
context. (Only the physical path is stored not the context itself.)

We set up a hierarchy of folders to show the behavior:

    >>> a = self.app._setObject('a', Folder('a'), set_owner=False)
    >>> b = self.app.a._setObject('b', Folder('b'), set_owner=False)
    >>> util = self.app.a.b._setObject(
    ...     'util', AQTestUtility('util'), set_owner=False)

Now we can make `a` a local component registry an register `util`
there. We expect the utility to implement getPhysicalPath and raise an
exception otherwise:

    >>> make_objectmanager_site(self.app.a)
    >>> setActiveSite(self.app.a)
    >>> sitemanager_a = self.app.a.getSiteManager()
    >>> sitemanager_a.registerUtility(self.app.a.b.util,
    ...                               name=u'with_aq_chain',
    ...                               provided=ITestUtility)
    Traceback (most recent call last):
    AttributeError: Component <Utility AQTestUtility "util"> does not implement getPhysicalPath, so register it unwrapped or implement this method.
    >>> import OFS.SimpleItem
    >>> class SITestUtility(OFS.SimpleItem.SimpleItem, TestUtility): pass
    >>> si_util = self.app.a.b._setObject('si_util', SITestUtility('si_util'))
    >>> sitemanager_a.registerUtility(self.app.a.b.si_util,
    ...                               name=u'with_aq_chain',
    ...                               provided=ITestUtility)

When we query the utility, it is returned with its original context:

    >>> si_util = sitemanager_a.getUtility(ITestUtility, name='with_aq_chain')
    >>> si_util
    <SITestUtility at /a/b/si_util>
    >>> si_util.getPhysicalPath()
    ('', 'a', 'b', 'si_util')
    >>> si_util.aq_chain
    [<SITestUtility at /a/b/si_util>, <Folder at /a/b>, <Folder at /a>, <Application at >]
    >>> si_util.absolute_url()
    'a/b/si_util'

    >>> zope.component.getUtility(ITestUtility, name='with_aq_chain')
    <SITestUtility at /a/b/si_util>

If we move a registered component (which has an absolute path) to new
place, the registration gets updated after calling registerUtility
again:

    >>> ignored = self.app.a.b._setObject('c', Folder('c'))
    >>> si_util = self.app.a.b.si_util.aq_base
    >>> self.app.a.b._delObject('si_util')
    >>> si_util.id = 'si_util_cped'
    >>> ignored = self.app.a.b.c._setObject('si_util_cped', si_util)
    >>> sitemanager_a.registerUtility(
    ...     self.app.a.b.c.si_util_cped,
    ...     name=u'with_aq_chain',
    ...     provided=ITestUtility)
    >>> zope.component.getUtility(ITestUtility, name='with_aq_chain')
    <SITestUtility at /a/b/c/si_util_cped>

And just to mix things up a bit. Getting back multiple utilities
should allow us to test aq, non-aq based components and components
having an absolute aqusition path.

First we have to register aq and non-aq based components in our
registry (a component having an absolute aqusition path already exists):

    >>> sitemanager_a.registerUtility(AQTestUtility('test'),
    ...                               name=u'aq_wrapped',
    ...                               provided=ITestUtility)
    >>> sitemanager_a.registerUtility(TestUtility('test'),
    ...                               name=u'hello_world',
    ...                               provided=ITestUtility)

We start with getUtilitiesFor():

    >>> utils = [x for x in sitemanager_a.getUtilitiesFor(ITestUtility)]
    >>> len(utils)
    3

    >>> nonaqutils = [(name, comp)
    ...               for name, comp in utils if not IAcquirer.providedBy(comp)]
    >>> len(nonaqutils)
    1
    >>> name, comp = nonaqutils[0]
    >>> Acquisition.aq_parent(comp) is None
    True

    >>> aqutils = [(name, comp)
    ...            for name, comp in utils if IAcquirer.providedBy(comp)]
    >>> len(aqutils)
    2
    >>> aqutils
    [(u'aq_wrapped', <Utility AQTestUtility "test">), 
     (u'with_aq_chain', <SITestUtility at /a/b/c/si_util_cped>)]

And then getAllUtilitiesRegisteredFor():

    >>> utils = [x for x in
    ...          sitemanager_a.getAllUtilitiesRegisteredFor(ITestUtility)]
    >>> len(utils)
    3

    >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)]
    >>> len(nonaqutils)
    1
    >>> comp = nonaqutils[0]
    >>> Acquisition.aq_parent(comp) is None
    True

    >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)]
    >>> len(aqutils)
    2
    >>> aqutils
    [<SITestUtility at /a/b/c/si_util_cped>, <Utility AQTestUtility "test">]

And registeredUtilities():

    >>> list(sitemanager_a.registeredUtilities())
    [UtilityRegistration(<PersistentComponents /a>, ITestUtility, u'with_aq_chain', si_util_cped, u''),
     UtilityRegistration(<PersistentComponents /a>, ITestUtility, u'aq_wrapped', <Utility AQTestUtility "test">, u''),
     UtilityRegistration(<PersistentComponents /a>, ITestUtility, u'hello_world', <Utility TestUtility "test">, u'')]

    >>> utils = [r.component for r in sitemanager_a.registeredUtilities()]
    >>> len(utils)
    3

    >>> nonaqutils = [comp for comp in utils if not IAcquirer.providedBy(comp)]
    >>> len(nonaqutils)
    1
    >>> comp = nonaqutils[0]
    >>> Acquisition.aq_parent(comp) is None
    True

    >>> aqutils = [comp for comp in utils if IAcquirer.providedBy(comp)]
    >>> len(aqutils)
    2
    >>> aqutils
    [<SITestUtility at /a/b/c/si_util_cped>, <Utility AQTestUtility "test">]

Nested Sites
------------

Whenever a component is queried using the component registry, the active
component registry (ie site manager) needs to be smart enough to check all
*parent* component registries as well.

Implementation-wise this means that each component registry needs to have an
appropriate ``__bases__`` attribute configured that is aware of containment
and (in the case of Zope 2) acquisition to some respect.

Start by creating some nested sites.

    >>> from five.localsitemanager import update_sitemanager_bases

    >>> folder1 = Folder('folder1')
    >>> make_objectmanager_site(folder1)
    >>> update_sitemanager_bases(folder1)

    >>> folder1_1 = Folder('folder1_1')
    >>> make_objectmanager_site(folder1_1)
    >>> ignored = folder1._setObject('folder1_1', folder1_1)
    >>> folder1_1 = folder1['folder1_1']
    >>> update_sitemanager_bases(folder1_1)

Now we check the actual next-site-lookup logic to make sure it's working.

    >>> from five.localsitemanager import find_next_sitemanager, get_parent

Needed to implement our own get_parent (zope3 has one) that is acquisition
aware.

    >>> get_parent(folder1)
    Traceback (most recent call last):
      ...
    TypeError: ('Not enough context...

    >>> get_parent(folder1_1)
    <Folder at folder1>

Any logic that sets up a site manager's ``__bases__`` will use the
``find_next_sitemanager`` function to figure out the next closest place
to look.

    >>> find_next_sitemanager(folder1) is None
    True

    >>> find_next_sitemanager(folder1_1)
    <PersistentComponents ...>

Now we make sure that that the ``__bases__`` have been setup appropriately.

    >>> folder1.getSiteManager().__bases__
    (<BaseGlobalComponents base>,)

    >>> folder1_1.getSiteManager().__bases__
    (<PersistentComponents ...>,)


Acquisition Context with Nested Sites
-------------------------------------

    >>> from zope.component import queryUtility

Register a utility with both of the nested site managers:

    >>> sm1 = folder1.getSiteManager()
    >>> sm1.registerUtility(AQTestUtility('util1'),
    ...                     name=u'util1',
    ...                     provided=ITestUtility)

    >>> sm1_1 = folder1_1.getSiteManager()
    >>> sm1_1.registerUtility(AQTestUtility('util1_1'),
    ...                           name=u'util1_1',
    ...                           provided=ITestUtility)

    >>> folder1_1.getSiteManager().__bases__
    (<PersistentComponents ...>,)

Lookup both utilities in the context of the first site manager:

    >>> setActiveSite(folder1)
    >>> util1 = queryUtility(ITestUtility, name=u'util1')
    >>> util1
    <Utility AQTestUtility "util1">

    >>> util1.aq_chain
    [<Utility AQTestUtility "util1">, <Folder at folder1>]

The second utility isn't available in the first site manager:

    >>> queryUtility(ITestUtility, name=u'util_1') is None
    True

Lookup both utilities in the context of the second site manager:

    >>> setActiveSite(folder1_1)
    >>> util1 = queryUtility(ITestUtility, name=u'util1')
    >>> util1
    <Utility AQTestUtility "util1">

We expect to get wrapped in the context of the site manager the utility is
registered with:

    >>> util1.aq_chain
    [<Utility AQTestUtility "util1">, <Folder at folder1>]

    >>> util1_1 = queryUtility(ITestUtility, name=u'util1_1')
    >>> util1_1
    <Utility AQTestUtility "util1_1">

    >>> util1_1.aq_chain
    [<Utility AQTestUtility "util1_1">, <Folder at folder1/folder1_1>, <Folder at folder1>]

Utilities stored with relative path
-----------------------------------

If we register a utility which has only a relative path, the path is
_not_ stored and the utility is returned relative to the registry. (In
the example we register folder_1/folder1_1/util in the registry of
folder_1.):

    >>> folder1_1._setObject('util', SITestUtility('util'), set_owner=False)
    'util'
    >>> sm1.registerUtility(folder1_1.util,
    ...                     name=u'util2',
    ...                     provided=ITestUtility)
    >>> sm1.getUtility(ITestUtility, name='util2')
    <SITestUtility at folder1/util>


Acquisition Context of Global Utilities
---------------------------------------

    >>> from zope.component import getGlobalSiteManager

Register a utility with both of the nested site managers:

    >>> gsm = getGlobalSiteManager()
    >>> gsm.registerUtility(AQTestUtility('globalutil1'),
    ...                     name=u'globalutil1',
    ...                     provided=ITestUtility)

    >>> sm1 = folder1.getSiteManager()
    >>> sm1.registerUtility(AQTestUtility('localutil1'),
    ...                     name=u'localutil1',
    ...                     provided=ITestUtility)

Lookup both utilities in the context of the global site manager:

    >>> clearSite()
    >>> globalutil1 = queryUtility(ITestUtility, name=u'globalutil1')
    >>> globalutil1
    <Utility AQTestUtility "globalutil1">

    >>> getattr(globalutil1, 'aq_chain', None) is None
    True

The local utility isn't available in the global site manager:

    >>> queryUtility(ITestUtility, name=u'localutil1') is None
    True

Lookup both utilities in the context of the local site manager:

    >>> setActiveSite(folder1)
    >>> globalutil1 = queryUtility(ITestUtility, name=u'globalutil1')
    >>> globalutil1
    <Utility AQTestUtility "globalutil1">

We expect the global utility to get no Acquisition context:

    >>> getattr(globalutil1, 'aq_chain', None) is None
    True

For the local utility we expect to get wrapped in the context of the local
site manager:

    >>> localutil1 = queryUtility(ITestUtility, name=u'localutil1')
    >>> localutil1
    <Utility AQTestUtility "localutil1">

    >>> localutil1.aq_chain
    [<Utility AQTestUtility "localutil1">, <Folder at folder1>]

Clean up
--------

    >>> from zope.testing.cleanup import cleanUp
    >>> clearSite()
    >>> cleanUp()
