# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.


__doc__ = """Glasnost Objects Proxy"""

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


import copy

import glasnost.common.context as context
import glasnost.common.faults as faults
from glasnost.common.ObjectsCommon import ObjectsCommonMixin
import glasnost.common.system as system
import glasnost.common.tools_new as commonTools

from DispatcherProxy import MultiCall, callServer, getApplicationToken
import kinds # Do not remove!
import things
from tools import *

register = things.register


class ObjectProxyMixin(things.ThingMixin):
    """Abstract class designed to be inherited."""

    def getProxy(self):
        """Return the object proxy class instance."""
        
        return getProxyForServerRole(self.serverRole)


class AdminWithoutWritersMixin(ObjectProxyMixin):
    def newDefaultObject(self):
        proxy = self.getProxy()
        return commonTools.newThing(
                'object', '%s.%s' % (proxy.serverRole, proxy.objectClassName))


class AdminMixin(AdminWithoutWritersMixin):
    pass


class Proxy(things.BaseThing):
    
    """Proxy base superclass.
    
    This call is designed to be inherited with other specific class in order to
    produce a fully fonctionnal Glasnost proxy class.

    Attribute:
    ==========

    *serverRole*:
        The class server role string.
        
    thingCategory*:
        The thing category string (here 'proxy').
   
    """
    
    serverRole = None
    thingCategory = 'proxy'

    def canGetAdmin(self, serverId = None):
        """Test if the proxy is able to get the admin instance.
        
        Keyword argument:
        =================

        *serverId*:
            The server ID string (default = None).
        
        Return:
        =======

        1 or true:
            The proxy is able to get the server admin instance.

        0 or false:
            The proxy is not able to get the server admin instance (incredible,
            isn't it ?).
        
        Exceptions
        ==========
        
        *AssertionError*:
            The server ID cannot be defined. (is None)
    
        *AttributeError*:
            The call procedure doesn't exist.
    
        *ProtocolError*:
            A protocol error in the underlying transport layer (such as a 404
            'not found' error if the server named by the URI does not exist).

        *Fault.\**:
            A Fault object encapsulates the content of an XML-RPC fault tag.

        *Exception*:
            All kind of exceptions raised by the called Remote Procedure.
        
        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'canGetAdmin',
                [serverId, getApplicationToken(), userToken])

    def canViewAll(self, serverId = None):
        return 0

    def getHandledRoles(self):
        """Return the proxy handled roles.
        
        This method seems not to be used.
        
        Return None.
        
        """
        
        return None

    def getServerId(self, serverId = None):
        """Get the dispatcher ID string.

        This method returns the ID string to use for XML RPC.
        
        Keyword argument:
        =================

        *serverId*:
            The server ID string.

        Return the callable dispatcher ID.
        
        Exception:
        ==========

        *AssertionError*:
            The given server ID doesn't begin by 'glasnost://'.
            
        *Standard Exception*:
            The server ID folowing the protocol specifier is not valid.

        *Exception*('Missing context'):
            The context does not contain information about the dispatcher Id.
  
        
        **TODO**:
        =========
       
        *FIXME*:
            ServerId is a misnamer. It should be replaced by dispatcherId.

        """
        
        if serverId is not None:
            return commonTools.makeApplicationId(serverId, self.serverRole)
        dispatcherId = context.getVar('dispatcherId')
        if dispatcherId is None:
            dispatcherId = context.getVar('defaultDispatcherId')
        if dispatcherId is None:
            raise Exception('Missing dispatcherId in context')
        return commonTools.makeApplicationId(dispatcherId, self.serverRole)


class AdministrableProxyMixin:
    
    """Abstract class designed to be inherited with a specialized class.

    The product of a multiple inheritance is a fonctionnal administrable proxy.
    All attributes can or must be overriden or extended, in fact, this class
    define their default values.

    """
    
    def canModifyAdmin(self, serverId = None):
        """Tests if the user can modify the proxy admin instance.

        Only an admin can modify the proxy admin class.

        Keyword Arguments:
        ==================
        
        *serverId*:
            The server Id string.

        Return values:
        ==============
        
        *0*: 
            If the admin set doesn't contain the user.
                
        *1*:
            If the admin set contains the user.
            There is no admin set defined.
            
        Exceptions:
        ===========
        
        *KeyError*:
            The virtual server ID does not correspond to a instanciated
            virtual server.

        *AssertionError*:
            The server ID cannot be defined. (is None)
    
        *AttributeError*:
            The call procedure doesn't exist.
    
        *ProtocolError*:
            A protocol error in the underlying transport layer (such as a 404
            'not found' error if the server named by the URI does not exist).

        *Fault.\**:
            A Fault object encapsulates the content of an XML-RPC fault tag.

        *Exception*:
            All kind of exceptions raised by the called Remote Procedure.
     
        Warning:
        ========

        An user token must be present in the context.
        
        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'canModifyAdmin',
                [serverId, getApplicationToken(), userToken])

    def getAdmin(self, serverId = None):
        """Get the server Admin class instance.

        Keyword argument:
        =================

        *serverId*:
            The server ID string to send the XML RPC.

        Return the Admin class instance.

        Exceptions:
        ===========

        *Exception*:
            
            + Unknown category.

            + Unknown class name.

        *All exception of the wanted admin class constructor.*

        *AssertionError*:
            The server ID cannot be defined. (is None)
    
        *AttributeError*:
            The call procedure doesn't exist.
    
        *ProtocolError*:
            A protocol error in the underlying transport layer (such as a 404
            'not found' error if the server named by the URI does not exist).

        *Fault.\**:
            A Fault object encapsulates the content of an XML-RPC fault tag.

        *Exception*:
            All kind of exceptions raised by the called Remote Procedure.

        """
        
        cache = context.getVar('cache')
        cacheKey = 'admin_%s' % self.serverRole
        if cache is not None and cache.has_key(cacheKey):
            adminImport = copy.deepcopy(cache[cacheKey])
            return commonTools.importThing(adminImport)
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        adminImport = callServer(
                serverId,
                'getAdmin',
                [serverId, getApplicationToken(), userToken])
        if cache is not None:
            cache[cacheKey] = copy.deepcopy(adminImport)
        return commonTools.importThing(adminImport)

    def getAdminSlotValueHolder(self, slotPath, serverId = None):
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        valueHolderImport = callServer(
                serverId,
                'getAdminSlotValueHolder',
                [serverId, getApplicationToken(), userToken, slotPath])
        return commonTools.importThing(valueHolderImport)

    def getAdminStringFromDigest(self, path, digest, serverId = None):
        serverId = self.getServerId(serverId = serverId)
        userToken = context.getVar('userToken', default = '')
        return iso8859_15(callServer(
                serverId,
                'getAdminStringFromDigest',
                [serverId, getApplicationToken(),
                 userToken, utf8(path), digest]))

    def hasAdminSlot(self, slotPath, serverId = None):
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'hasAdminSlot',
                [serverId, getApplicationToken(), userToken, slotPath])

    def isAdmin(self, serverId = None):
        """Indicate whether the caller is an administrator of this server."""
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'isAdmin',
                [serverId, getApplicationToken(), userToken])

    def modifyAdmin(self, admin, serverId = None):
        """Modify the admin virtual server object.
        
        Keyword argument:
        ==================

        *serverId*:
            The server ID string to send the XML RPC.

        
        Returns:
        ========

        The admin object new version.

        Exceptions:
        ===========

        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *faults.\**:
            An XML-RPC fault.
        
        *UnknownException*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        adminExport = admin.exportToXmlRpc()
        admin.version = callServer(
                serverId,
                'modifyAdmin',
                [serverId, getApplicationToken(), userToken, adminExport])
        return None

    def newAdmin(self, keywords = None):
        """Instanciate a new Admin class.
        
        Keyword argument:
        =================

        *keywords*:
            May be used in subclasses to create different objects on the basis
            of some keyword.
        
        Exceptions:
        ===========

        *Exception*:
            
            + Unknown category.

            + Unknown class name.

        *All exception of the wanted admin class constructor.*

        """

        return commonTools.newThing(
                'object', '%s.%s' % (self.serverRole, self.adminClassName))

    def setAdminSlotValueHolder(self, slotPath, valueHolder, serverId = None):
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        valueHolderExport = valueHolder.exportToXmlRpc()
        callServer(
                serverId,
                'setAdminSlotValueHolder',
                [serverId, getApplicationToken(), userToken, slotPath,
                 valueHolderExport])


class ObjectsProxy(ObjectsCommonMixin, AdministrableProxyMixin, Proxy):
    
    """Proxy of the Objects server.
    
    This class is used to communicate with the Object server.
    
    """
    
    def addObject(self, object, serverId = None):
        """Add an object to the server.
        
        Keyword arguments:
        ==================

        *objectImport*:
            The object instance to add.
        
        *serverId*:
            The server ID string (default = None).
        
        Returns:
        ========

        The new object ID.
        
        Exceptions:
        ===========

        *AssertionError*:
            The server ID cannot be defined. (is None)
    
        *AttributeError*:
            The call procedure doesn't exist.
    
        *ProtocolError*:
            A protocol error in the underlying transport layer (such as a 404
            'not found' error if the server named by the URI does not exist).

        *Fault.\**:
            A Fault object encapsulates the content of an XML-RPC fault tag.

        *Exception*:
            All kind of exceptions raised by the called Remote Procedure.

        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *UnknowException*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.
            - The virtual server ID does not correspond to a instanciated
              virtual server.

        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        objectExport = object.exportToXmlRpc()
        objectId =  callServer(
                serverId,
                'addObject',
                [serverId, getApplicationToken(), userToken, objectExport])
        return objectId

    def canAddObject(self, serverId = None):
        """Test if adding an object is allowed.

        To add an object to the server, the user must be an admin, or in the
        writers' set.
        
        Keyword arguments:
        ==================

        *serverId*:
            The server ID string (default = None).

        Returns:
        ========

        *1 or true*:
            The user can add an object to the server.

        *0 or false*:
            The user cannot add an object to the server.

        Exceptions:
        ===========
        
        *AssertionError*:
            The server ID cannot be defined. (is None)
    
        *AttributeError*:
            The call procedure doesn't exist.
    
        *ProtocolError*:
            A protocol error in the underlying transport layer (such as a 404
            'not found' error if the server named by the URI does not exist).

        *Fault.\**:
            A Fault object encapsulates the content of an XML-RPC fault tag.

        *Exception*:
            All kind of exceptions raised by the called Remote Procedure.

        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *UnknowException*:
            
            - The thing category 'object' doesn't exists. (very grave !)
            
            - The adminImport __thingName__ key is not a valid Thing name.

            - The virtual server ID does not correspond to a instanciated
              virtual server.

        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'canAddObject',
                [serverId, getApplicationToken(), userToken])

    def canCloneObject(self, objectId):
        """Test if the user can duplicate an existing object.

        To be able to clone an object, the user must have a valid userToken,
        must be able to retrieve the object and be able to add an object to the
        server.

        Keyword argument:
        =================

        *objectId*:
            The object ID string to clone.
        
        Returns:
        ========

        *1 or true*:
            The user can duplicate the object.

        *0 or false*:
            The user cannot duplicate the object.

        Exceptions:
        ===========

        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *UnknowException*:
            
            - The thing category 'object' doesn't exists. (very grave !)
            
            - The adminImport __thingName__ key is not a valid Thing name.

            - The virtual server ID does not correspond to a instanciated
              virtual server.

        *AssertionError*:
            The object ID string is not a glasnost service ('glasnost://').

        *IndexError*:
            The given object ID is incomplete.

        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = commonTools.extractServerId(objectId)
        return userToken and self.canGetObject(objectId) \
               and self.canAddObject(serverId = serverId)

    def canDeleteObject(self, objectId):
        """Test if deleting a specified object is allowed.

        To delete an object on the server, the user must be an admin, or in the
        writers' set and the object must exist.
        
        Keyword arguments:
        ==================

        *objectId*:
            The object ID string to delete.

        Returns:
        ========

        *1 or true*:
            The user can delete the object.

        *0 or false*:
            The user cannot delete the object.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *UnknowException*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.
            - The virtual server ID does not correspond to a instanciated
              virtual server.

        *AssertionError*:
            The object ID string is not a glasnost service ('glasnost://').

        *IndexError*:
            The given object ID is incomplete.

        *AssertionError*:
            The server ID cannot be defined. (is None)
    
        *AttributeError*:
            The call procedure doesn't exist.
    
        *ProtocolError*:
            A protocol error in the underlying transport layer (such as a 404
            'not found' error if the server named by the URI does not exist).

        *Fault.\**:
            A Fault object encapsulates the content of an XML-RPC fault tag.

        *Exception*:
            All kind of exceptions raised by the called Remote Procedure.

        """

        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'canDeleteObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])

    def canGetObject(self, objectId):
        """Test if the user can retrieve a specified object.

        To get an object on the server, the user must be an admin, or in the
        object or admin server class instance readers' set or the client
        application can get it, and the object must exist.
        
        Keyword arguments:
        ==================

        *objectId*:
            The object ID string to retrieve.

        Returns:
        ========

        *1 or true*:
            The user can get the object.

        *0 or false*:
            The user cannot get the object.

        Exceptions:
        ===========
        
        *AssertionError*:
            The server ID cannot be defined. (is None)
    
        *AttributeError*:
            The call procedure doesn't exist.
    
        *ProtocolError*:
            A protocol error in the underlying transport layer (such as a 404
            'not found' error if the server named by the URI does not exist).

        *Fault.\**:
            A Fault object encapsulates the content of an XML-RPC fault tag.

        *Exception*:
            All kind of exceptions raised by the called Remote Procedure.

        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.
            - The virtual server ID does not correspond to a instanciated
              virtual server.

        *AssertionError*:
            The object ID string is not a glasnost service ('glasnost://').

        *IndexError*:
            The given object ID is incomplete.

        """
        
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'canGetObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])

    def canGetObjects(self, serverId = None):
        """Test if the user can retrieve the server objects.

        To be able to see the objects stored in the server the use must be an
        admin, or the admin server class instance doesn't use readers set, or
        the user is in the admin class instance readers set.
        
        If an user cannot get the server objects, he could be able to retrieve
        a specified object.

        The admin readers set is used to limit the allowed users to list the
        objects on the server.
       
        Keyword argument:
        ==================

        *serverId*:
            The server ID string (default = None).

        Returns:
        ========

        *1 or true*:
            The user can get the objects.

        *0 or false*:
            The user cannot get the objects.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *UnknowException*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *faults.UnknownApplicationToken*:
            The given application token is not in the dispatcher virtual server
            ids dictionnary.
        
        """

        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'canGetObjects',
                [serverId, getApplicationToken(), userToken])

    def canModifyObject(self, objectId):
        """Test if the user can modify a specified object.

        The user must be admin, or in the object writers set in order to be
        able to modify the object.
        
        Keyword arguments:
        ==================

        *objectId*:
            The object ID string designing the object to modify.

        Returns:
        ========

        *1 or true*:
            The user can modify the object.

        *0 or false*:
            The user cannot modify the object.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.

        *UnknowException*:
        
            - The thing category 'object' doesn't exists. (very grave !)

            - The adminImport __thingName__ key is not a valid Thing name.

            - The virtual server ID does not correspond to a instanciated
              virtual server.

        *AssertionError*:
            The object ID string is not a glasnost service ('glasnost://').

        *IndexError*:
            The given object ID is incomplete.

        """
        
        userToken = context.getVar('userToken', default = '')
        cache = context.getVar('temporaryCache')
        cacheKey = 'canModifyObject-%s-%s' % (objectId, userToken)
        if cache is not None:
            try:
                return cache[cacheKey]
            except KeyError:
                pass
        result  = callServer(
                    commonTools.extractServerId(objectId),
                    'canModifyObject',
                    [commonTools.extractServerId(objectId),
                     getApplicationToken(), userToken, objectId])
        if cache is not None:
            cache[cacheKey] = result
        return result


    def canUseObject(self, objectId):
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'canUseObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])

    def deleteObject(self, objectId):
        """Test if the user can delete a specified object.

        The user must be admin, or in the object writers set in order to be
        able to modify the object.
        
        Keyword arguments:
        ==================

        *objectId*:
            The object ID string designing the object to modify.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *faults.MissingItem*:
            The object does not exist.

        *UnknowException*:
            - The thing category 'object' doesn't exists. (very grave !)

            - The adminImport __thingName__ key is not a valid Thing name.
            
            - The virtual server ID does not correspond to a instanciated
              virtual server.

        *AssertionError*:
            The object ID string is not a glasnost service ('glasnost://').

        *IndexError*:
            The given object ID is incomplete.

        """

        userToken = context.getVar('userToken', default = '')
        callServer(
                commonTools.extractServerId(objectId),
                'deleteObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])
        
    def getIdsAllowedToDeleteObject(self, objectId):
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'getIdsAllowedToDeleteObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])

    def getIdsAllowedToModifyObject(self, objectId):
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'getIdsAllowedToModifyObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])
 
    def getLastObjectIds(self, objectsCount, possibleReadersSet,
                         possibleWritersSet, serverId = None):
        """Retrieve the last accessed objects ids.

        Keyword arguments:
        ==================

        *objectCount*:
            The maximum number of object ids to retrieve.

        *possibleReaderSet*:
            Useless.

        *possibleWriterSet*:
            Useless.

        *serverId*:
            The server ID string (default = None).
        
        Returns:
        ========

        *Object ID sequence*:
            The sequence of the last accessed object ids.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *faults.MissingItem*:
            The object does not exist.

        *standard Exception*:
            
            - The thing category 'object' doesn't exists. (very grave !)
            
            - The adminImport __thingName__ key is not a valid Thing name.
            
            - The virtual server ID does not correspond to a instanciated
              virtual server.

        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        if not possibleReadersSet:
            possibleReadersSet = [system.generalPublicId]
        if not possibleWritersSet:
            possibleWritersSet = [system.generalPublicId]
        return callServer(
                serverId,
                'getLastObjectIds',
                [serverId, getApplicationToken(), userToken, objectsCount,
                 possibleReadersSet, possibleWritersSet])

    def getLastObjects(
        self, objectsCount, possibleReadersSet, possibleWritersSet,
        requiredSlotNames = None, serverId = None):
        """Retrieve the last accessed objects.

        Keyword arguments:
        ==================

        *objectCount*:
            The maximum number of object ids to retrieve.

        *possibleReaderSet*:
            Useless.

        *possibleWriterSet*:
            Useless.

        *serverId*:
            The server ID string (default = None).
        
        Returns:
        ========

        *Objects sequence*:
            The sequence of the last accessed objects.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *faults.MissingItem*:
            The object does not exist.

        *standard Exception*:
            
            - The thing category 'object' doesn't exists. (very grave !)
            
            - The adminImport __thingName__ key is not a valid Thing name.
            
            - The virtual server ID does not correspond to a instanciated
              virtual server.

        """
        
        objectIds = self.getLastObjectIds(
                objectsCount, possibleReadersSet, possibleWritersSet,
                serverId = serverId)
        multiCall = MultiCall()
        for objectId in objectIds:
            self.getPartialObject(objectId, requiredSlotNames,
                                  multiCall = multiCall)
        return [lazyObject() for lazyObject in multiCall.call()]

    def getObject(self, objectId, multiCall = None):
        """Retrieve a specified object instance.

        Keyword arguments:
        ==================

        *objectId*:
            The ID of the object instance to retrieve.

        *multiCall*:
            If (1 or true), this call is stacked in order to perform a
            multicall.

        Returns:
        ========

        *Object instance*:
            The wanted object instance.

        *None*:
            The call is designed for multi call, so the object instance will be
            retrieved by the multi call returns sequence.

        Exceptions:
        ===========
        
        *faults.MissingItem*:
            The object ID string does not correspond to a existing object
            instance.

        *faults.UserAccessDenied*:
            The user is not allowed to read the object isntance.

        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *faults.UnknownApplicationToken*:
            The given application token is not in the dispatcher virtual server
            ids dictionnary.

        *AssertionError*:
            The object ID string is not a glasnost service ('glasnost://').

        *IndexError*:
            The given object ID is incomplete.

        """

        cache = context.getVar('cache')
        if cache is not None and cache.has_key(objectId):
            result = copy.deepcopy(cache[objectId])
            if multiCall:
                multiCall.addEarlyResult(result)
                return None
            return result
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'getObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId],
                resultHandler = self.getObject_handleResult,
                multiCall = multiCall)

    def getObject_handleResult(self, lazyObject):
        """Return an object instance from a XML RPC object dictionnary.

        Keyword argument:
        ================

        *lazyObject*:
            The result representation instance.
        
        Return the object instance defined in the lazyObject.
        
        """
        
        objectImport = lazyObject()
        object = commonTools.importThing(objectImport)
        cache = context.getVar('cache')
        if cache is not None and object.canCache():
            cache[object.id] = copy.deepcopy(object)
        return object

    def getObjectIds(self, serverId = None):
        """Retrieve the ids of all server objects.

        Returns:
        ========

        *Object ID sequence*:
            The sequence of the server objects ids.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *faults.MissingItem*:
            The object does not exist.

        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *KeyError*:
            The virtual server ID does not correspond to a instanciated
            virtual server.

        """

        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)

        cache = context.getVar('temporaryCache')
        cacheKey = 'getObjectIds_%s' % serverId
        if cache is not None and cache.has_key(cacheKey):
            return cache[cacheKey]
        
        result = callServer(
                serverId,
                'getObjectIds',
                [serverId, getApplicationToken(), userToken])
    
        if cache is not None:
            cache[cacheKey] = result
        return result


    def getObjectIdsWithCriteria(self, criteria, serverId = None):
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'getObjectIdsWithCriteria',
                [serverId, getApplicationToken(), userToken, criteria])

    def getObjectLabelAndLanguage(self, objectId, multiCall = None):
        """Retrieve the object label and language in sequence.

        Keyword argument:
        =================

        *objectId*:
            The ID of the object.

        Exceptions:
        ===========

        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *faults.MissingItem*:
            The object does not exist.

        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *KeyError*:
            The virtual server ID does not correspond to a instanciated
            virtual server.

        """

        userToken = context.getVar('userToken', default = '')
        if not objectId:
            if multiCall is None:
                return '', None
            else:
                return multiCall.addEarlyResult(('', None))

        cache = context.getVar('cache')
        if cache is not None and cache.has_key(objectId):
            object = cache[objectId]
            result = (object.getLabel(), object.getLabelLanguage())
            if multiCall is None:
                return result
            else:
                return multiCall.addEarlyResult(result)

        def handleResult(lazyLabelAndLanguage):
            label, language = lazyLabelAndLanguage()
            if not language:
                language = None
            return iso8859_15(label), language
        return callServer(
                commonTools.extractServerId(objectId),
                'getObjectLabelAndLanguage',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId],
                resultHandler = handleResult,
                multiCall = multiCall)

    def getObjectLabelsTranslated(
            self, objectIds, destinationLanguages, serverId = None):
        """Get the objects labels in the specified languages.
        
        Keyword Arguments:
        ==================

        *objectIds*:
            The sequence of IDs of the objects of the wanted labels.

        *destinationLanguages*:
            The sequence of the wanted language strings.

        Return the labels dictionnary (the key is the object ID).

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *KeyError*:
            The virtual server ID does not correspond to a instanciated
            virtual server.

        """
        
        cache = context.getVar('temporaryCache')
        if cache is not None:
            cacheKey = 'getObjectLabelsTranslated-%r-%r' % (
                    objectIds, destinationLanguages)
            try:
                return cache[cacheKey]
            except KeyError:
                pass

        multiCall = MultiCall()
        for objectId in objectIds:
            try:
                self.getObjectLabelAndLanguage(objectId, multiCall = multiCall)
            except faults.UnknownDispatcherInId:
                objectIds.remove(objectId)
        lazyLabelsAndLanguages = multiCall.call()
        labelsAndLanguages = {}
        for i in range(len(objectIds)):
            try:
                labelsAndLanguages[objectIds[i]] = lazyLabelsAndLanguages[i]()
            except faults.MissingItem:
                pass
        translationsProxy = getProxyForServerRole('translations')
        labels = {}
        if translationsProxy:
            multiCall = MultiCall()
            for objectId, labelAndLanguage in labelsAndLanguages.items():
                label, language = labelAndLanguage
                try:
                    translationsProxy.getTranslation(
                        label, objectId, 'self.getLabel()', language,
                        destinationLanguages, multiCall = multiCall)
                except faults.UnknownDispatcherInId:
                    labels[objectId] = objectId
                    del labelsAndLanguages[objectId]
            lazyTranslations = multiCall.call()
            objectIds = labelsAndLanguages.keys()
            for i in range(len(objectIds)):
                labels[objectIds[i]] = lazyTranslations[i]()
        else:
            for objectId, labelAndLanguage in labelsAndLanguages.items():
                labels[objectId] = labelAndLanguage[0]
        defaultDispatcherId = context.getVar('defaultDispatcherId')
        dispatcherId = context.getVar('dispatcherId')
        if not commonTools.extractDispatcherId(objectId) in [
                defaultDispatcherId, dispatcherId, 'glasnost://help',
                'glasnost://system']:
            for id, label in labels.items():
                label += _(' (at <%s>)') \
                         % extractApplicationHostNameAndPort(objectId)
                labels[id] = label
        
        if cache is not None:
            cache[cacheKey] = labels
        return labels

    def getObjectLabelTranslated(self, objectId, destinationLanguages):
        """Get the object label in the specified languages.
        
        Keyword Arguments:
        ==================

        *objectId*:
            The sequence of IDs of the objects of the wanted labels.

        *destinationLanguages*:
            The sequence of the wanted language strings.

        Returns:
        ========
        
        *The translated label*

        *An empty tring*:
            The object Id is None.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *faults.MissingItem*:
            The object does not exist.

        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *KeyError*:
            The virtual server ID does not correspond to a instanciated
            virtual server.

        """
        
        if not objectId:
            return ''
        label, language = self.getObjectLabelAndLanguage(objectId)
        if destinationLanguages[0] == context.getVar('readLanguages')[0] and \
                language == 'en' and hasattr(_, 'im_self') and \
                hasattr(_.im_self, '_catalog') and \
                _.im_self._catalog.has_key(label):
            return _(label)
        translationsProxy = getProxyForServerRole('translations')
        if translationsProxy:
            labelTranslated = translationsProxy.getTranslation(
                    label, objectId, 'self.getLabel()', language,
                    destinationLanguages )
        else:
            labelTranslated = label
        defaultDispatcherId = context.getVar('defaultDispatcherId')
        dispatcherId = context.getVar('dispatcherId')
        if not commonTools.extractDispatcherId(objectId) in [
                defaultDispatcherId, dispatcherId, 'glasnost://help',
                'glasnost://system']:
            labelTranslated += _(' (at <%s>)') \
                               % extractApplicationHostNameAndPort(objectId)
        return labelTranslated

    def getObjects(self, requiredSlotNames = None, serverId = None):
        """Get the server objects instances.

        Keyword arguments:
        ==================

        *requiredSlotNames*:
            The wanted slots names sequence.

        *serverId*:
            The server ID string (default = None).

        Return the objects dictionnary (key is the object ID).

        Exceptions:
        ===========

        *faults.UserAccessDenied*:
            The user is not in the admin set.
        
        *faults.MissingItem*:
            The object does not exist.

        *standard Exception*:
            - The thing category 'object' doesn't exists. (very grave !)
            - The adminImport __thingName__ key is not a valid Thing name.

        *KeyError*:
            The virtual server ID does not correspond to a instanciated
            virtual server.
        
        """
        
        objectIds = self.getObjectIds(serverId = serverId)
        multiCall = MultiCall()
        for objectId in objectIds:
            self.getPartialObject(
                    objectId, requiredSlotNames, multiCall = multiCall)
        lazyObjects = multiCall.call()
        objects = {}
        for i in range(len(objectIds)):
            objects[objectIds[i]] = lazyObjects[i]()
        return objects

    def getObjectsCount(self, serverId = None):
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'getObjectsCount',
                [serverId, getApplicationToken(), userToken])
    
    def getObjectsFromIds(self, objectIds,
                          requiredSlotNames = None, serverId = None):
        multiCall = MultiCall()
        for objectId in objectIds:
            if requiredSlotNames:
                self.getPartialObject(objectId,
                        requiredSlotNames, multiCall = multiCall)
            else:
                self.getObject(objectId, multiCall = multiCall)
        lazyObjects = multiCall.call()
        objects = {}
        for i in range(len(objectIds)):
            objects[objectIds[i]] = lazyObjects[i]()
        return objects

    def getObjectsWithCriteria(self, criteria, requiredSlotNames = None,
                               serverId = None):
        objectIds = self.getObjectIdsWithCriteria(
                        criteria, serverId = serverId)
        multiCall = MultiCall()
        for objectId in objectIds:
            self.getPartialObject(
                    objectId, requiredSlotNames, multiCall = multiCall)
        lazyObjects = multiCall.call()
        objects = {}
        for i in range(len(objectIds)):
            objects[objectIds[i]] = lazyObjects[i]()
        return objects


    def getObjectSlotValueHolder(self, objectId, slotPath):
        userToken = context.getVar('userToken', default = '')
        valueHolderImport = callServer(
                commonTools.extractServerId(objectId),
                'getObjectSlotValueHolder',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId, slotPath])
        return commonTools.importThing(valueHolderImport)

    def getObjectStringFromDigest(self, objectId, path, digest):
        userToken = context.getVar('userToken', default = '')
        return iso8859_15(callServer(
                commonTools.extractServerId(objectId),
                'getObjectStringFromDigest',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId, utf8(path), digest]))

    def getObjectWithVersion(self, objectId, versionNumber):
        userToken = context.getVar('userToken', default = '')
        objectImport = callServer(
                commonTools.extractServerId(objectId),
                'getObjectWithVersion',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId, versionNumber])
        return commonTools.importThing(objectImport)
        
    def getPartialObject(self, objectId, requiredSlotNames, multiCall = None):
        """Retrieve partial object instance from the server.
        
        Keyword arguments:
        ==================

        *objectId*:
            The ID of the wanted object partial informations.

        *requiredSlotNames*:
            The wanted slot names sequence.

        *multiCall*:
            If true (or 1), the call is stacked in order to perform a multi
            calls.

       
        Return the wanted partial object instance.
        
        Exceptions:
        ===========

        *UnknowError*:
            - A required slot name is a private slot name.
           
            - A element of the required slots names sequence is not a string.
        
        """
        
        if not requiredSlotNames:
            return self.getObject(objectId, multiCall = multiCall)
        cacheKey = requiredSlotNames[:]
        cacheKey.sort()
        cacheKey = '%s_%s' % (objectId, str(cacheKey)) 
        def handleResult(lazyObject, self = self, cacheKey = cacheKey):
            objectImport = lazyObject()
            object = commonTools.importThing(objectImport)
            cache = context.getVar('cache')
            if cache is not None and object.canCache():
                cache[cacheKey] = copy.deepcopy(object)
            return object
        cache = context.getVar('cache')
        if cache is not None and cache.has_key(objectId):
            result = copy.deepcopy(cache[objectId])
            if multiCall:
                multiCall.addEarlyResult(result)
                return None
            else:
                return result
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'getPartialObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId, requiredSlotNames],
                resultHandler = handleResult,
                multiCall = multiCall)

    def getRevisionsInfos(self, objectId):
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'getRevisionsInfos',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])

    def hasObject(self, objectId):
        """Test if the server contains an object.

        Keyword argument:
        =================
        
        *objectId*:
            The object Id to look up.

        Returns:
        ========
        
        *1 or true*:
            The server owns the object with the ID *objectId*.

        *0 of false*:
            The server does not own the object with the ID *objectId*.

        Exception:
        ==========

        *UnknowException*:
            The virtual server ID does not correspond to a instanciated virtual
            server.

        """

        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'hasObject',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId])

    def hasObjectSlot(self, objectId, slotPath):
        userToken = context.getVar('userToken', default = '')
        return callServer(
                commonTools.extractServerId(objectId),
                'hasObjectSlot',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId, slotPath])

    def isObjectId(self, id):
        """Test if the given string is a valid object ID.
        
        A valid object ID begin with the server ID string, followed by sub id
        (separated by slashes).
        
        Keyword argument:
        =================

        *id*:
            The id string to test.

        Return 1 or true if the given string is a valid object ID.
        """
        return id.startswith(self.getServerId() + '/')

    def modifyObject(self, object):
        """Modify an object on the server.

        The client object is also modified to be in sync with the server object
        (same object version).
        
        Keyword argument:
        =================

        *object*:
            The modified object instance.
        
        Returns:
        ========

        **None**

        Exceptions:
        ===========

        *faults.MissingItem*:
            The specified object does not exist.
            
        *faults.UserAccessDenied*:
            The client is not allowed to modify the object.

        """
        
        userToken = context.getVar('userToken', default = '')
        objectExport = object.exportToXmlRpc()
        object.version = callServer(
                commonTools.extractServerId(object.id),
                'modifyObject',
                [commonTools.extractServerId(object.id), getApplicationToken(),
                 userToken, objectExport])
        return None

    def modifyPartialObject(self, object, givenSlotNames):
        """Partialy modify a object.
        
        Keyword arguments:
        ==================

        *object*:
            The modified object.

        *givenSlotNames*:
            The sequence of the slot names to modify. If empty, the object is
            completly modified.
        
        Returns:
        ========

        **None**
        
        Exceptions:
        ===========

        *faults.MissingId*:
            The modified object instance has no id attribute.
        
        *faults.MissingItem*:
            The specified object does not exist.
            
        *faults.UserAccessDenied*:
            The client is not allowed to modify the object.

        *faults.ReadOnlyObject*:
            The object cannot be modified.

        """

        if not givenSlotNames:
            return self.modifyObject(object)
        givenSlotNames = givenSlotNames[:]
        for slotName in ['id', 'version']:
            if not slotName in givenSlotNames and object.hasSlotName(slotName):
                slot = object.getSlot(slotName)
                if slot.getKind().isImportable():
                    assert hasattr(object, slotName) \
                           and getattr(object, slotName) is not None
                    givenSlotNames.append(slotName)
        userToken = context.getVar('userToken', default = '')
        objectExport = object.exportToXmlRpc(
            requiredSlotNames = givenSlotNames)
        object.version = callServer(
                commonTools.extractServerId(object.id),
                'modifyPartialObject',
                [commonTools.extractServerId(object.id), getApplicationToken(),
                 userToken, objectExport, givenSlotNames])
        return None

    def newObject(self, keywords = None):
        """Instanciate a new object.

        The class of the new object instance is stored in the *objectClassName*
        attribute.
        
        Keyword argument:
        =================

        *keywords*: 
            May be used in subclasses to create different objects on the basis
            of some keyword.

        Return the new object instance.
        
        """
        
        return commonTools.newThing(
                'object', '%s.%s' % (self.serverRole, self.objectClassName))

    def searchObjects(self, serverId = None, objectIds = [],
                      sortRule = '', **searchKeywords):
        objectIds = self.searchObjectIds(serverId, objectIds = objectIds,
                                         sortRule = sortRule, **searchKeywords)
        multiCall = MultiCall()
        for objectId in objectIds:
            self.getObject(objectId, multiCall = multiCall)
        lazyObjects = multiCall.call()
        objects = {}
        for i in range(len(objectIds)):
            objects[objectIds[i]] = lazyObjects[i]()
        return objects

    def searchObjectIds(self, serverId = None, multiCall = None,
                        objectIds = [], sortRule = '', **searchKeywords):
        """Find object Ids.
        
        Search keywords syntax:
        =======================
        
        + 'attribute_name_EQ = value' means attribute=value 
        
        + 'attribute_name_LT = value' means attribute < value 
        
        + 'attribute_name_GT = value' means attribute > value 
        
        + 'attribute_name_LE = value' means attribute <= value 
        
        + 'attribute_name_GE = value' means attribute >= value 
        
        + 'attribute_name_IN = value' means value contains attribute 
        
        + 'attribute_name_CT = value' means value in attribute
        
        + 'searchTerms = value' is for plain text blank-separated search terms

        Keyword argument:
        =================
        
        *searchDict*:
            The search string following the search keywords syntax.
        
        Returns:
        ========

        *Object ID sequence*:
            The sequence of the corresponding objects ids.

        Exceptions:
        ===========
        
        *faults.UserAccessDenied*:
            The user cannont get the objects list.
            
        *faults.MissingItem*:
            The object does not exist.

        *UnknowException*:
            - Invalid syntax in the *searchDict* string.
            
            - The virtual server ID does not correspond to a instanciated
              virtual server.
 
        """
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        searchDict = {}
        for sk in searchKeywords.keys():
            searchDict[sk] = searchKeywords[sk]
        if not objectIds and not searchDict:
            return self.getObjectIds()
        return callServer(
                serverId,
                'searchObjectIds',
                [serverId, getApplicationToken(), userToken,
                 objectIds, sortRule, searchDict],
                 multiCall = multiCall)

    def searchPartialObjects(self, serverId = None, requiredSlotNames = [],
                             objectIds = [],  sortRule = '', **searchKeywords):
        objectIds = self.searchObjectIds(serverId, objectIds = objectIds,
                                         sortRule = sortRule, **searchKeywords)
        multiCall = MultiCall()
        for objectId in objectIds:
            self.getPartialObject(objectId, requiredSlotNames,
                                  multiCall = multiCall)
        lazyObjects = multiCall.call()
        objects = {}
        for i in range(len(objectIds)):
            objects[objectIds[i]] = lazyObjects[i]()
        return objects

    def setObjectSlotValueHolder(self, objectId, slotPath, valueHolder):
        userToken = context.getVar('userToken', default = '')
        valueHolderExport = valueHolder.exportToXmlRpc()
        callServer(
                commonTools.extractServerId(objectId),
                'setObjectSlotValueHolder',
                [commonTools.extractServerId(objectId), getApplicationToken(),
                 userToken, objectId, slotPath, valueHolderExport])

    def sortObjectIds(self, objectIds, serverId = None, sortRule = None,
                      forceIds = 0):
        """Sort object Ids by specified attribute.

        The object id are sorted by *sortRule* string.
        This string is an attribute name.
        Supported names are:
            + 'modificationTime': Descendent sort by modification time.
            
            + 'id': Ascendent sort by Id.
            
            + 'date': Descendent sort by the date attribute.
                
            + '': Ascendent sort by label.

        Keyword arguments:
        ==================

        *objectIds*:
            The object ids sequence to sort.

        *sortRule*:
            The specified sort attribute.

        Return:
        =======

        The sorted sequence of object ids.

        Exceptions:
        ===========

        *faults.UserAccessDenied*:
            The user cannont get the objects list.
            
        *faults.MissingItem*:
            The object does not exist.

        *UnknowException*:
             - Error in comparaisons.
             
             - The virtual server ID does not correspond to a instanciated
               virtual server.

        """
        
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        if not objectIds and not forceIds:
            return []
        if sortRule is None:
            sortRule = ""
        return callServer(
                serverId,
                'sortObjectIds',
                [serverId, getApplicationToken(), userToken,
                 objectIds, sortRule])

    def subscribe(self, method, when, callbackName, serverId = None):
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'subscribe',
                [serverId, getApplicationToken(), userToken, method, when,
                 callbackName])

    def unsubscribe(self, method = '_ALL_', when= 'any ', callbackName = 'any',
                    serverId = None):
        userToken = context.getVar('userToken', default = '')
        serverId = self.getServerId(serverId = serverId)
        return callServer(
                serverId,
                'unsubscribe',
                [serverId, getApplicationToken(), userToken, method, when,
                 callbackName])

