# -*- 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 Server"""

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


import copy
import cPickle
import fcntl
import getopt
import os
import re
import shutil
import signal
import sys
import time
from threading import Lock
import traceback
import UserDict

import glasnost

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

import kinds # Do not remove!
import things
from tools import *
import values
from xmlrpcServer import FastServer, FastThreadingServer

from glasnost.proxy.CacheProxy import invalidateValue
from glasnost.proxy.DispatcherProxy import getApplicationId, \
        getApplicationToken, registerServer, registerVirtualServer
from glasnost.proxy.GroupsProxy import getSetContainedIds
from glasnost.proxy.tools import getProxyForServerRole


register = things.register


class ObjectServerMixin(things.ThingMixin):
    """Abstract class designed to be inherited with a specialized ObjectCommon.

    The product of this multiple inheritance is a fonctionnal Object.
    All methods can be overriden or extended, in fact, this class define their
    default behavior.
    
    The client that want to operate on the object is usually a other server.
    """
    
    id_kind_isAutomaticallyModified = 1
    # The method id_kind_setAutomaticalValue() is defined below.

    version_kind_isAutomaticallyModified = 1
    # The method version_kind_setAutomaticalValue() is defined below.

    def __getinitargs__(self):
        """Ensure that __init__ is called during unpickling."""
        
        return ()

    def acquireNonCore(self, objectDirectoryPath = None,
                       dataDirectoryPath = None, parentSlot = None):
        """Load the data associated with the object.
       
        The datas are loaded, but the metadata are in the object instance.
        The datas are usually big things like files, text, etc...
        
        Keyword arguments:
        ==================

        *objectDirectoryPath*:
            The sub-directory reserved to the object instance.

        *dataDirectoryPath*:
            The data directory of the server serving the object.

        *parentSlot*:
            This is the slot where the object is (if applicable).
       
        No return.
        ----------

        """
        
        if objectDirectoryPath is None:
            server = self.getServer()
            virtualServerId = server.computeVirtualServerId(self.id)
            virtualServer = server.getVirtualServer(virtualServerId)
            if dataDirectoryPath is None:
                dataDirectoryPath = virtualServer.dataDirectoryPath
            objectsDirectoryPath = os.path.join(
                dataDirectoryPath, server.applicationRole)
            serverHostNameAndPort, serverRole, localId = \
                    commonTools.splitId(self.id)
            objectDirectoryPath = os.path.join(
                objectsDirectoryPath, localId)
        things.ThingMixin.acquireNonCore(
            self, objectDirectoryPath, parentSlot = parentSlot)

    def canBeCreatedByClient(self):
        """Indicate whether the client application can create an object."""

        return 0

    def canBeDeletedByClient(self):
        """Indicate whether the client application can delete the object."""
        
        return 0
    
    def canBeGottenByClient(self):
        """Indicate whether the client application can get the object."""
        
        return 0

    def canBeModified(self):
        """Indicate whether the object can be modified."""

        return 1

    def canBeModifiedByClient(self):
        """Indicate whether the client application can modify the object."""
        
        return 0

    def checkAddIsPossible(self):
        """Indicate whether the object can be added to the server dictionnary.

        """
        
        for slotName in self.getSlotNames():
            slot = self.getSlot(slotName)
            kind = slot.getKind()
            value = slot.getValue()
            kind.checkModelValue(slot, value)

    def checkModifyIsPossible(self, changes, givenSlotNames = None):
        """Indicate whether the article can be replaced by another version.
        
        Keyword arguments:
        ==================

        *changes*:
            The new version of the object.

        *givenSlotNames*:
            The sequence of attributes (formally SlotName in Glasnost)
        
        """
        
        if self.version != changes.version:
            raise faults.WrongVersion()
        # We need to call changes.getModifySlotNames() instead of
        # self.getModifySlotNames() because changes and self can have a
        # different class.
        if self.__class__ != changes.__class__:
            class_ = self.__class__
            self.__class__ = changes.__class__
        else:
            class_ = None
        try:
            for slotName in changes.getModifySlotNames():
                slot = self.getSlot(slotName)
                kind = slot.getKind()
                if not kind.isModifiable:
                    continue
                if kind.isAutomaticallyModified:
                    continue
                if not kind.isImportable():
                    continue
                if givenSlotNames and not slotName in givenSlotNames:
                    continue
                changesSlot = changes.getSlot(slotName)
                value = changesSlot.getValue()
                kind.checkModelValue(slot, value)
        finally:
            if class_ is not None:
                self.__class__ = class_

    def getServer(self):
        """Return the server string."""
        
        return context.getVar('server')

    def id_kind_setAutomaticalValue(self, slot, init = 0):
        # Note: Here, self is the Id kind, not the object.
        if init:
            object = slot.getObject()
            server = object.getServer()
            object.id = server.getNextId()

    def initPartial(self, partial, givenSlotNames = None):
        """Instanciate a partial object.

        Designed to create a object with not all attributes set.
        This is used in order to modify an existing object with version
        information.
        
        Keyword arguments:
        ==================

        *partial*:
            The object to be partialy instanciated.

        *giveSlotNames*:
            The sequence of attributes (formally SlotName in Glasnost).
        
        No return.
        ----------
        
        """
        
        pass

    def markAsDirty(self):
        """Add object to the To Save list."""

        server = self.getServer()
        if self.id is None:
            virtualServerId = context.getVar('applicationId')
        else:
            virtualServerId = server.computeVirtualServerId(self.id)
        virtualServer = server.getVirtualServer(virtualServerId)
        virtualServer.markObjectAsDirty(self)
        
    def modify(self, changes, givenSlotNames = None):
        """Override object attributes with new one's.
        
        Keyword arguments:
        ==================

        *changes*:
            The (partial or not) object with the new attributes (SlotValue).

        *givenSlotName*:
            The Slot (attributes) names to modify.
        
        No return.
        ----------
        
        """
        
        self.version += 1
        if self.__class__ != changes.__class__:
            self.__class__ = changes.__class__
        # We need to call changes.getModifySlotNames() instead of
        # self.getModifySlotNames() because changes and self can have a
        # different class.
        for slotName in changes.getModifySlotNames():
            slot = self.getSlot(slotName)
            kind = slot.getKind()
            if not kind.isModifiable:
                continue
            if kind.isAutomaticallyModified:
                kind.setAutomaticalValue(slot)
                continue
            if not kind.isImportable():
                continue
            if givenSlotNames and not slotName in givenSlotNames:
                continue
            changesSlot = changes.getSlot(slotName)
            value = changesSlot.getValue()
            slot.setValue(value)

    def removeNonCore(self, objectDirectoryPath = None,
                      dataDirectoryPath = None, parentSlot = None):
        """Remove the object datas from memory.

        The datas are usually big things like files, text, etc...
        
        Keyword arguments:
        ==================

        *objectDirectoryPath*:
            The sub-directory reserved to the object instance.

        *dataDirectoryPath*:
            The data directory of the server serving the object.

        *parentSlot*:
            This is the slot where the object is (if applicable).
        
        No return.
        ----------
    
        """

        if objectDirectoryPath is None:
            server = self.getServer()
            virtualServerId = server.computeVirtualServerId(self.id)
            virtualServer = server.getVirtualServer(virtualServerId)
            if dataDirectoryPath is None:
                dataDirectoryPath = virtualServer.dataDirectoryPath
            if not os.access(dataDirectoryPath, os.F_OK):
                return
            objectsDirectoryPath = os.path.join(
                dataDirectoryPath, server.applicationRole)
            if not os.access(objectsDirectoryPath, os.F_OK):
                return
            serverHostNameAndPort, serverRole, localId = \
                    commonTools.splitId(self.id)
            objectDirectoryPath = os.path.join(objectsDirectoryPath, localId)
        if not os.access(objectDirectoryPath, os.F_OK):
            return
        for slotName in self.getSlotNames(parentSlot = parentSlot):
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            kind = slot.getKind()
            if kind.useFileStorage:
                shutil.rmtree(objectDirectoryPath)

    def saveNonCore(self, objectDirectoryPath = None, dataDirectoryPath = None,
                    parentSlot = None):
        """Save the object datas.

        The datas are usually big things like files, text, etc...
        
        Keyword arguments:
        ==================

        *objectDirectoryPath*:
            The sub-directory reserved to the object instance.

        *dataDirectoryPath*:
            The data directory of the server serving the object.

        *parentSlot*:
            This is the slot where the object is (if applicable).
        
        No return.
        ----------
    
        """

        if objectDirectoryPath is None:
            server = self.getServer()
            virtualServerId = server.computeVirtualServerId(self.id)
            virtualServer = server.getVirtualServer(virtualServerId)
            if dataDirectoryPath is None:
                dataDirectoryPath = virtualServer.dataDirectoryPath
            if not os.access(dataDirectoryPath, os.F_OK):
                os.mkdir(dataDirectoryPath)
                os.chmod(dataDirectoryPath, 0750)
            objectsDirectoryPath = os.path.join(
                    dataDirectoryPath, server.applicationRole)
            # The creation of the directory is needed here, because
            # kinds.saveValueToFile doesn't create it.
            if not os.access(objectsDirectoryPath, os.F_OK):
                os.mkdir(objectsDirectoryPath)
                os.chmod(objectsDirectoryPath, 0750)
            serverHostNameAndPort, serverRole, localId = \
                    commonTools.splitId(self.id)
            objectDirectoryPath = os.path.join(objectsDirectoryPath, localId)
            if os.access(objectDirectoryPath, os.F_OK):
                try:
                    shutil.rmtree(objectDirectoryPath)
                except OSError:
                    # FIXME: If objectDirectoryPath is not a directory, do
                    # nothing.
                    # Happens for articles, books, uploadFiles... which
                    # should be converted to the new way of handling 
                    #print 'Not a directory: %s' % objectDirectoryPath
                    pass
        things.ThingMixin.saveNonCore(
            self, objectDirectoryPath, parentSlot = parentSlot)

    def setAutomaticalSlots(self, parentSlot = None):
        """Set automatic slots values.
        
        *parentSlot*:
            This is the slot where the object is (if applicable).
        
        """
        
        for slotName in self.getModifySlotNames():
            slot = self.getSlot(slotName)
            kind = slot.getKind()
            if kind.isAutomaticallyModified:
                kind.setAutomaticalValue(slot, init = 1)

    def version_kind_setAutomaticalValue(self, slot, init = 0):
        # Note: Here, self is the kind of version, not the object.
        pass


class AdminServerWithoutWritersMixin(ObjectServerMixin):
    def newDefaultObject(self):
        server = self.getServer()
        return commonTools.newThing(
                'object',
                '%s.%s' % (server.serverRole, server.objectClassName))


class AdminServerMixin(AdminServerWithoutWritersMixin):
    pass


class VirtualServer:
    coreSaveAction = None
    dataDirectoryPath = None
    isReadOnly = 0
    virtualServerId = None
    lock = None

    def __getinitargs__(self):
        # Ensure that __init__ is called during unpickling.
        return ()

    def __getstate__(self):
        # Don't pickle the threading locks.
        state = self.__dict__.copy()
        if state.has_key('adminEmailAddress'):
            del state['adminEmailAddress']
        if state.has_key('dataDirectoryPath'):
            del state['dataDirectoryPath']
        if state.has_key('lock'):
            del state['lock']
        return state

    def __init__(self):
        self.lock = Lock()

    def __setstate__(self, state):
        emptyVirtualServer = context.getVar('emptyVirtualServer')
        if emptyVirtualServer is not None:
            self.__dict__.update(emptyVirtualServer.__dict__)
        self.__dict__.update(state)

    def convertIds(self, sourceDispatcherId, destinationDispatcherId):
        if context.getVar('server').hasMultipleVirtualServers:
            sourceHostName = extractApplicationHostName(sourceDispatcherId)
            destinationHostName = extractApplicationHostName(
                    destinationDispatcherId)
            if self.dataDirectoryPath is not None \
                   and self.dataDirectoryPath.endswith(sourceHostName):
                self.dataDirectoryPath = self.dataDirectoryPath[
                        :-len(sourceHostName)] + destinationHostName
            newVirtualServerId = self.virtualServerId.replace(
                    sourceDispatcherId, destinationDispatcherId)
            if newVirtualServerId != self.virtualServerId:
                self.virtualServerId = newVirtualServerId

    def exportNonCore(self, exportDirectoryPath):
        pass

    def importNonCore(self, importDirectoryPath):
        pass

    def init(self):
        virtualServerId = context.getVar('applicationId')
        assert virtualServerId is not None
        self.virtualServerId = virtualServerId

        self.loadConfigOptions()
        self.loadCommandLineOptions()

    def initFromOldData(self, data):
        pass

    def loadCommandLineOptions(self):
        pass

    def loadConfigOptions(self):
        server = context.getVar('server')
        dispatcherId = commonTools.extractDispatcherId(self.virtualServerId)
        hostName = extractApplicationHostName(dispatcherId)

        if server.useDataFile:
            if not server.hasMultipleVirtualServers:
                self.dataDirectoryPath = server.dataDirectoryPath
            else:
                self.dataDirectoryPath = commonTools.getConfig(
                        dispatcherId, 'DataDirectoryPath')
                if not self.dataDirectoryPath:
                    self.dataDirectoryPath = commonTools.getConfig(
                            'Misc', 'DispatcherDataDirectoryPath',
                            vars = {'hostname': hostName},
                            default = '/var/lib/glasnost/%s' % hostName)
        elif self.dataDirectoryPath is not None:
            del self.dataDirectoryPath

        self.adminEmailAddress = commonTools.getConfig(
                dispatcherId, 'AdminEmailAddress',
                default = commonTools.getConfig(
                        'Mail', 'Admin', default = 'root@localhost'))

    def markAllAsDirtyFIXME(self):
        """Mark all server data as dirty, so that they will be saved.

        This method is provided for compatibility, and should not be used
        anymore.

        FIXME: Remove every occurences of this method ASAP.

        """

        self.markCoreAsDirty()

    def markCoreAsDirty(self):
        self.coreSaveAction = 'save'

    def removeIds(self, rolesToKeep):
        pass

    def savePendingRequests(self, saveNeeded = 0):
        if self.coreSaveAction is not None:
            try:
                del self.coreSaveAction
            except AttributeError:
                pass # probably done in another thread
            saveNeeded = 1
        if saveNeeded:
            context.getVar('server').saveVirtualServer(self)
        return saveNeeded


class AdministrableVirtualServer(VirtualServer):
    admin = None
    adminSaveAction = None

    def convertIds(self, sourceDispatcherId, destinationDispatcherId):
        VirtualServer.convertIds(
                self, sourceDispatcherId, destinationDispatcherId)
        self.admin.convertIds(sourceDispatcherId, destinationDispatcherId)

    def init(self):
        VirtualServer.init(self)
        server = context.getVar('server')
        self.admin = server.newAdmin()
        virtualServerId = context.getVar('applicationId')
        self.admin.id = '%s/__admin__' % virtualServerId

    def markAdminAsDirty(self, admin):
        self.adminSaveAction = 'save'

    def markAllAsDirtyFIXME(self):
        VirtualServer.markAllAsDirtyFIXME(self)
        self.markAdminAsDirty(self.admin)

    def removeIds(self, rolesToKeep):
        VirtualServer.removeIds(self, rolesToKeep)
        self.admin.removeIds(rolesToKeep)

    def savePendingRequests(self, saveNeeded = 0):
        if self.adminSaveAction is not None:
            try:
                del self.adminSaveAction
            except AttributeError:
                pass # probably done in another thread
            saveNeeded = 1
        VirtualServer.savePendingRequests(self, saveNeeded = saveNeeded)


class ObjectsVirtualServer(AdministrableVirtualServer):
    allObjectsSaveAction = None # To remove ASAP. See saveAllFIXME method.
    nextLocalId = 1
    objects = None
    objectSaveActions = None
    storeRevisions = 1

    def canLoadObjectCore(self, objectId):
        return self.objects.has_key(objectId)

    def convertIds(self, sourceDispatcherId, destinationDispatcherId):
        AdministrableVirtualServer.convertIds(
                self, sourceDispatcherId, destinationDispatcherId)
        for id, object in self.objects.items():
            newId = id.replace(sourceDispatcherId, destinationDispatcherId)
            if newId != id:
                del self.objects[id]
                self.objects[newId] = object
            object.convertIds(sourceDispatcherId, destinationDispatcherId)
            sourceHostName = extractApplicationHostName(sourceDispatcherId)
            destinationHostName = extractApplicationHostName(
                destinationDispatcherId)
            if self.dataDirectoryPath is not None \
               and self.dataDirectoryPath.endswith(destinationHostName):
                sourceDataDirectoryPath = self.dataDirectoryPath[
                    :-len(destinationHostName)] + sourceHostName
            else:
                sourceDataDirectoryPath = self.dataDirectoryPath
            object.acquireNonCore(dataDirectoryPath = sourceDataDirectoryPath)
            object.convertIds(sourceDispatcherId, destinationDispatcherId)
            object.saveNonCore()
            object.releaseNonCore()

    def exportNonCore(self, exportDirectoryPath):
        AdministrableVirtualServer.exportNonCore(self, exportDirectoryPath)
        for object in self.objects.values():
            object.acquireNonCore()
            object.saveNonCore(dataDirectoryPath = exportDirectoryPath)
            object.releaseNonCore()

    def getNextLocalId(self):
        result = self.nextLocalId
        self.nextLocalId += 1
        return result

    def getObjectWithVersion(self, objectId, versionNumber):
        server = context.getVar('server')
        rcFilePath = os.path.join(
                self.dataDirectoryPath,
                server.applicationName + '.revisions.pickle')
        try:
            fd = open(rcFilePath)
        except IOError:
            return None
        while 1:
            try:
                revision = cPickle.load(fd)
            except EOFError:
                break
            if revision['object'].id != objectId:
                continue
            if revision['object'].version != versionNumber:
                continue
            return revision['object']
        return None

    def getRevisionsInfos(self, objectId):
        server = context.getVar('server')
        rcFilePath = os.path.join(
                self.dataDirectoryPath,
                server.applicationName + '.revisions.pickle')
        try:
            fd = open(rcFilePath)
        except IOError:
            return []
        revisions = []
        while 1:
            try:
                revision = cPickle.load(fd)
            except EOFError:
                break
            if revision['object'].id != objectId:
                continue
            revision['version'] = revision['object'].version
            del revision['object']
            revisions.append(revision)
        fd.close()
        return revisions

    def importNonCore(self, importDirectoryPath):
        AdministrableVirtualServer.importNonCore(self, importDirectoryPath)
        for object in self.objects.values():
            object.acquireNonCore(dataDirectoryPath = importDirectoryPath)
            object.saveNonCore()
            object.releaseNonCore()

    def init(self):
        AdministrableVirtualServer.init(self)
        self.objects = {}

    def initFromOldData(self, data):
        AdministrableVirtualServer.initFromOldData(self, data)
        (self.nextLocalId, self.objects, self.admin) = data

    def loadObjectCore(self, objectId):
        if not self.objects.has_key(objectId):
            raise faults.MissingItem(objectId)
        return self.objects[objectId]

    def markAllAsDirtyFIXME(self):
        AdministrableVirtualServer.markAllAsDirtyFIXME(self)
        self.allObjectsSaveAction = 'save'

    def markObjectAsDeleted(self, objectId):
        if self.objectSaveActions is None:
            self.objectSaveActions = {}
        self.objectSaveActions[objectId] = 'delete'

    def markObjectAsDirty(self, object):
        if self.objectSaveActions is None:
            self.objectSaveActions = {}
        self.objectSaveActions[object.id] = 'save'

    def removeIds(self, rolesToKeep):
        AdministrableVirtualServer.removeIds(self, rolesToKeep)
        for object in self.objects.values():
            object.removeIds(rolesToKeep)

    def saveObjectRevision(self, objectId):
        # TODO: should have a 'diff' mode
        #   (UploadFilesServer.revisions.pickle is 150M after two weeks of
        #    www.bxlug.be)
        if self.isReadOnly:
            return
        server = context.getVar('server')
        if not server.useDataFile:
            return
        object = self.loadObjectCore(objectId)
        object.acquireNonCore()
        try:
            data = {
                    'time': time.time(),
                    'userId': getProxyForServerRole('identities').getUserId(),
                    'object': object,
                    }
            if not os.access(self.dataDirectoryPath, os.F_OK):
                os.mkdir(self.dataDirectoryPath)
                os.chmod(self.dataDirectoryPath, 0750)
            rcFilePath = os.path.join(
                    self.dataDirectoryPath,
                    server.applicationName + '.revisions.pickle')
            self.lock.acquire()
            rcFile = open(rcFilePath, 'a')
            cPickle.dump(data, rcFile)
            rcFile.close()
            os.chmod(rcFilePath, 0640)
            self.lock.release()
        finally:
            object.releaseNonCore()

    def savePendingRequests(self, saveNeeded = 0):
        if self.storeRevisions and self.objectSaveActions is not None:
            for objectId in self.objectSaveActions.keys():
                if self.objectSaveActions[objectId] != 'save':
                    continue
                self.saveObjectRevision(objectId)
            try:
                del self.objectSaveActions
            except AttributeError:
                pass # probably done in another thread
            saveNeeded = 1
        # To remove as soon as the saveAllFIXME method will be removed.
        if self.allObjectsSaveAction is not None:
            try:
                del self.allObjectsSaveAction
            except AttributeError:
                pass # probably done in another thread
            saveNeeded = 1
        AdministrableVirtualServer.savePendingRequests(
                self, saveNeeded = saveNeeded)
            

class ObjectsMultiIdPersistence(UserDict.UserDict):
    """This class is usefull to store object with multiple id like
       glasnost://site.tld/serverName/1:2:4:2 where only the first id
       is used to store the object. In this example 1.
    """
    def has_key(self, ids):
        return UserDict.UserDict.has_key(
            self, commonTools.splitMultiId(ids)[0])

    def get(self, ids, failobj=None):
        return UserDict.UserDict.get(
            self, commonTools.splitMultiId(ids)[0], failobj)

    def __contains__(self, ids):
        return UserDict.UserDict.__contains__(
            self, commonTools.splitMultiId(ids)[0])

    def __delitem__(self, ids):
        return UserDict.UserDict.__delitem__(
            self, commonTools.splitMultiId(ids)[0])

    def __getitem__(self, ids):
        return UserDict.UserDict.__getitem__(
            self, commonTools.splitMultiId(ids)[0])

    def __setitem__(self, ids, value):
        return UserDict.UserDict.__setitem__(
            self, commonTools.splitMultiId(ids)[0], value)
 

class ObjectsMultiIdVirtualServer(ObjectsVirtualServer):
    """Use this class for value of VirtualServer attribute for your server
       class. when you use multiId. When you use this class, you don't need
       to override methode like getObjectXmlRpc, deleteObject...
    """
    def init(self):
        print 'ObjectsMultiIdVirtualServer.init'
        ObjectsVirtualServer.init(self)
        self.objects = ObjectsMultiIdPersistence()
   

class Server(things.BaseThing, applications.Application):
    VirtualServer = None
    dataDirectoryPath = None
    dispatcherId = None
    hasMultipleVirtualServers = 1
    hostName = None
    isThreaded = 1
    port = None
    rpcServer = None
    thingCategory = 'server'
    useDataFile = 1
    virtualServer = None
    virtualServers = None

    def __init__(self):
        self.checkGlasnostFileVersionNumber()

    def addDispatcher(self, newDispatcherId):
        """Add a new dispatcher to the server."""

        # TODO: check on clientToken (only dispatcher we registered to should
        # be allowed)

        newVirtualServerId = commonTools.makeApplicationId(
                newDispatcherId, self.applicationRole)
        
        try:
            registerVirtualServer(self.hostName, self.port, newVirtualServerId)
        except faults.RoleNotInProfiles:
            return

        if self.hasMultipleVirtualServers:
            newVirtualServer = self.initVirtualServer(newVirtualServerId)
            self.virtualServers[newVirtualServerId] = newVirtualServer
            newVirtualServer = self.loadVirtualServer(newVirtualServer)
            self.virtualServers[newVirtualServerId] = newVirtualServer
            
        context.push(
                applicationId = newVirtualServerId,
                )
        context.getVar('applicationTokens')[newVirtualServerId] = \
                getApplicationToken()
        context.pull()

    def canGetAdmin(self):
        """Indicate whether the caller is able to get the admin instance.

        This method needs to be overriden. It returns a boolean.

        """

        return 0

    def checkGlasnostFileVersionNumber(self):
        classes = commonTools.getC3ClassLinearization(self.__class__)
        upgradeMethodNames = []
        for class_ in classes:
            for attributeName in class_.__dict__.keys():
                if attributeName.startswith('upgradeVirtualServer_'):
                    if not attributeName in upgradeMethodNames:
                        upgradeMethodNames.append(attributeName)
        upgradeMethodNames.sort()
        if len(upgradeMethodNames) > 0:
            lastMethodVersionNumber = upgradeMethodNames[-1][
                    len('upgradeVirtualServer_'):]
            if lastMethodVersionNumber > glasnost.fileVersionNumber:
                raise Exception(
                        'The constant fileVersionNumber in shared/__init__'
                        ' needs to be upgraded (%s >= %s).' % (
                        lastMethodVersionNumber, glasnost.fileVersionNumber))

    def computeVirtualServerId(self, objectId):
        return commonTools.makeApplicationId(objectId, self.applicationRole)

    def convertVirtualServersIds(self, sourceDispatcherId,
                                 destinationDispatcherId):
        sourceVirtualServerId = commonTools.makeApplicationId(
                sourceDispatcherId, self.applicationRole)
        destinationVirtualServerId = commonTools.makeApplicationId(
                destinationDispatcherId, self.applicationRole)
        if not self.virtualServers.has_key(sourceVirtualServerId):
            print 'Missing virtual server for %s' % sourceDispatcherId
            return 2
        destinationVirtualServer = self.virtualServers[
            sourceVirtualServerId].__class__()
        for key, value in self.virtualServers[
                sourceVirtualServerId].__dict__.items():
            if not destinationVirtualServer.__dict__.has_key(key):
                destinationVirtualServer.__dict__[key] = value
        self.virtualServers[destinationVirtualServerId
                            ] = destinationVirtualServer
        destinationVirtualServer.convertIds(
            sourceDispatcherId, destinationDispatcherId)
        del self.virtualServers[sourceVirtualServerId]
        destinationVirtualServer.markAllAsDirtyFIXME()
        return None

    def exportVirtualServer(self, virtualServerId, exportDirectoryPath):
        if not self.useDataFile:
            return None
        if not self.hasMultipleVirtualServers:
            return None
        import gnosis.xml.pickle as glasnostPickle
        virtualServer = self.getVirtualServer(virtualServerId)
        if not os.access(exportDirectoryPath, os.F_OK):
            os.mkdir(exportDirectoryPath)
            os.chmod(exportDirectoryPath, 0750)
        xmlFilePath = os.path.join(
            exportDirectoryPath, self.applicationName + '.xml')
        xmlFile = open(xmlFilePath, 'wb')
        os.chmod(xmlFile, 0640)
        fcntl.lockf(xmlFile, fcntl.LOCK_EX)
        xmlFile.write(glasnost.fileVersionNumber + '\n')
        virtualServer.lock.acquire()
        glasnostPickle.dump(virtualServer, xmlFile, 0)
        virtualServer.lock.release()
        fcntl.lockf(xmlFile, fcntl.LOCK_UN)
        xmlFile.close()
        virtualServer.exportNonCore(exportDirectoryPath)
        return None

    def fillEmptyVirtualServer(self, virtualServer):
        pass

    def getVirtualServer(self, virtualServerId):
        """Return the Virtal server instance.

        Get the virtual server instance associated to the virtual server ID in
        the virtualServers dictionnary.
        
        Keyword argument:
        =================

        *virtualServerId*:
            The virtual server ID string.

        Return:
        =======

        *VirtualServer*: The virtual server instance.

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

        *faults.UnknownServerId*:
            The virtual server ID does not correspond to a instanciated
            virtual server.
            
        """

        if not self.hasMultipleVirtualServers:
            return self.virtualServer
        if not self.virtualServers.has_key(virtualServerId):
            raise faults.UnknownServerId(virtualServerId)
        return self.virtualServers[virtualServerId]

    def handleGetopt(self):
        try:
            opts, args = getopt.getopt(
                sys.argv[1:],
                'd:hi:s:x:y:v',
                ['destination=', 'export=', 'help', 'import=', 'source=',
                 'system=', 'verbose'])
        except getopt.GetoptError:
            self.usage()
            return 2
        if len(args) != 0:
            self.usage()
            return 2

        commandLineContext = context.get(_level = 'commandLine')
        destinationDispatcherId = None
        doExport = 0
        doImport = 0
        doSystem = 0
        exportDirectoryPath = None
        importDirectoryPath = None
        sourceDispatcherId = None
        for o, a in opts:
            if o in ('-d', '--destination'):
                destinationDispatcherId = a
                if destinationDispatcherId[-1] == '/':
                    destinationDispatcherId = destinationDispatcherId[:-1]
                if not destinationDispatcherId.startswith('glasnost://') \
                   or '/' in destinationDispatcherId[len('glasnost://'):]:
                    print 'Wrong destination = %s' % destinationDispatcherId
                    return 2
            if o in ('-h', '--help'):
                usage()
                return 0
            if o in ( '-v', '--verbose'):
                commandLineContext.setVar('verbose', 1)
            if o in ('-s', '--source'):
                sourceDispatcherId = a
                if sourceDispatcherId[-1] == '/':
                    sourceDispatcherId = sourceDispatcherId[:-1]
                if not sourceDispatcherId.startswith('glasnost://') \
                   or '/' in sourceDispatcherId[len('glasnost://'):]:
                    print 'Wrong source = %s' % sourceDispatcherId
                    return 2
            if o in ('-i', '--import'):
                importDirectoryPath = a
                doImport = 1
            if o in ('-x', '--export'):
                exportDirectoryPath = a
                doExport = 1
            if o in ('-y', '--system'):
                doSystem = 1
                systemRoles = eval(a)
                if type(systemRoles) not in [type(()), type([])]:
                    print 'Wrong system roles = %s' % systemRoles
                    return 2
        if doExport:
            if not sourceDispatcherId:
                print 'Missing source to export'
                return 2
            if not self.useDataFile:
                return 0
            self.load(dispatcherId = sourceDispatcherId)
            virtualServerId = commonTools.makeApplicationId(
                    sourceDispatcherId, self.applicationRole)
            hostName = extractApplicationHostName(virtualServerId)
            exitCode = self.exportVirtualServer(
                virtualServerId, os.path.join(exportDirectoryPath, hostName))
            if exitCode is not None:
                return exitCode
            return 0
        if doImport:
            if not destinationDispatcherId:
                print 'Missing destination to import into'
                return 2
            if not self.useDataFile:
                return 0
            virtualServerId = commonTools.makeApplicationId(
                    destinationDispatcherId, self.applicationRole)
            hostName = extractApplicationHostName(virtualServerId)
            virtualServer = self.importVirtualServer(
                virtualServerId, os.path.join(importDirectoryPath, hostName))
            if virtualServer is not None:
                self.virtualServers = {
                    virtualServerId: virtualServer,
                    }
                virtualServer.markAllAsDirtyFIXME()
                virtualServer.savePendingRequests()
            return 0
        if sourceDispatcherId and destinationDispatcherId:
            # Convert all the ids from sourceDispatcherId to
            # destinationDispatcherId.
            # If doSystem is set, also perform other modifications.
            if not self.useDataFile:
                return 0
            if doSystem and not self.applicationRole in systemRoles:
                return 0
            self.load(dispatcherId = sourceDispatcherId)
            exitCode = self.convertVirtualServersIds(
                    sourceDispatcherId, destinationDispatcherId)
            if exitCode is not None:
                return exitCode
            if doSystem:
                exitCode = self.removeVirtualServerHistory(
                    destinationDispatcherId)
                if exitCode is not None:
                    return exitCode
                exitCode = self.removeIdsFromVirtualServer(
                    destinationDispatcherId, systemRoles)
                if exitCode is not None:
                    return exitCode
            destinationVirtualServerId = commonTools.makeApplicationId(
                    destinationDispatcherId, self.applicationRole)
            destinationVirtualServer = self.getVirtualServer(
                    destinationVirtualServerId)
            destinationVirtualServer.savePendingRequests()
            return 0
        return None

    def handleSignal(self, signalNumber, stackFrame):
        if signalNumber == signal.SIGINT:
            raise KeyboardInterrupt
        if signalNumber == signal.SIGHUP:
            print 'Received signal SIGHUP: reopening logs'
            self.setLogFile()
            return
        if signalNumber == signal.SIGTERM:
            print 'Received signal SIGTERM: exiting.'
            os._exit(0)

    def importVirtualServer(self, virtualServerId, importDirectoryPath):
        dispatcherId = commonTools.extractDispatcherId(virtualServerId)
        context.push(
                applicationId = virtualServerId,
                dispatcherId = dispatcherId,
                emptyVirtualServer = None,
                )
        try:
            virtualServer = self.VirtualServer()
            virtualServer.init()
            context.setVar('emptyVirtualServer', virtualServer)

            if self.useDataFile:
                if not self.hasMultipleVirtualServers:
                    return None

                import gnosis.xml.pickle as glasnostPickle
                glasnostPickle.setParanoia(0)
                pickleFilePath = os.path.join(
                    importDirectoryPath, self.applicationName + '.pickle')
                xmlFilePath = os.path.join(
                    importDirectoryPath, self.applicationName + '.xml')
                rcFile = None
                if os.access(xmlFilePath, os.F_OK):
                    rcFile = open(xmlFilePath, 'rb')
                    import gnosis.xml.pickle as glasnostPickle
                    glasnostPickle.setParanoia(0)
                elif os.access(pickleFilePath, os.F_OK):
                    import cPickle as glasnostPickle
                    rcFile = open(pickleFilePath, 'rb')
                if rcFile is None:
                    self.fillEmptyVirtualServer(virtualServer)
                else:
                    fcntl.lockf(rcFile, fcntl.LOCK_SH)
                    version = self.readFileVersion(rcFile)
                    #print 'file:',  rcFile
                    data = glasnostPickle.load(rcFile)
                    fcntl.lockf(rcFile, fcntl.LOCK_UN)
                    rcFile.close()
                    if isinstance(data, VirtualServer):
                        virtualServer = data
                    else:
                        virtualServer.initFromOldData(data)
                    self.upgradeVirtualServer(virtualServer, version)
        finally:
            context.pull()
        virtualServer.importNonCore(importDirectoryPath)
        return virtualServer

    def init(self):
        pass

    def initContextOriginalOptions(self):
        applications.Application.initContextOriginalOptions(self)

        originalContext = context.get(_level = 'original')

        originalContext.setVar('applicationTokens', {})

        originalContext.setVar('server', self)
        originalContext.setVar('serverRole', self.applicationRole)

    def initVirtualServer(self, virtualServerId):
        assert virtualServerId is not None
        dispatcherId = commonTools.extractDispatcherId(virtualServerId)
        context.push(
                applicationId = virtualServerId,
                dispatcherId = dispatcherId,
                )
        try:
            virtualServer = self.VirtualServer()
            virtualServer.init()
        finally:
            context.pull()
        return virtualServer

    def launch(self, applicationName, applicationRole):
        self.applicationName = applicationName
        self.applicationRole = applicationRole

        applications.Application.launch(self)

        if self.port is None:
            if context.getVar('verbose'):
                sys.stderr.write(
                    'No server port set. Not launching server (%s).\n'
                    % applicationName)
            sys.exit(1)

        if not context.getVar('verbose'):
            goDaemon()
            self.setLogFile()
        self.init()
        self.load()
        self.startRpcServer()
        self.run()

    def load(self, dispatcherId = None):
        if self.hasMultipleVirtualServers:
            self.virtualServers = {}
            if dispatcherId is not None:
                virtualServerId = commonTools.makeApplicationId(
                        dispatcherId, self.applicationRole)
                virtualServer = self.initVirtualServer(virtualServerId)
                self.virtualServers[virtualServerId] = virtualServer
                virtualServer = self.loadVirtualServer(virtualServer)
                self.virtualServers[virtualServerId] = virtualServer
        else:
            virtualServerId = context.getVar('applicationId')
            self.virtualServer = self.initVirtualServer(virtualServerId)
            self.virtualServer = self.loadVirtualServer(self.virtualServer)

    def loadConfigOptions(self):
        applications.Application.loadConfigOptions(self)

        configContext = context.get(_level = 'config')

        self.dispatcherId = configContext.getVar('dispatcherId')

        self.hostName = commonTools.getConfig(
                self.applicationName, 'ServerHostName')
        if not self.hostName:
            self.hostName = 'localhost'

        try:
            self.port = eval(commonTools.getConfig(
                    self.applicationName, 'ServerPort'), {})
        except TypeError:
            if self.port is not None:
                del self.port

        if self.useDataFile:
            self.dataDirectoryPath = commonTools.getConfig(
                    'Misc', 'DataDirectoryPath')

    def loadVirtualServer(self, virtualServer):
        context.push(
                applicationId = virtualServer.virtualServerId,
                dispatcherId = commonTools.extractDispatcherId(
                        virtualServer.virtualServerId),
                emptyVirtualServer = virtualServer,
                )
        if self.useDataFile:
            try:
                pickleFilePath = os.path.join(
                        virtualServer.dataDirectoryPath,
                        self.applicationName + '.pickle')
                xmlFilePath = os.path.join(
                        virtualServer.dataDirectoryPath,
                        self.applicationName + '.xml')
                rcFile = None
                if os.access(xmlFilePath, os.F_OK):
                    rcFile = open(xmlFilePath, 'rb')
                    import gnosis.xml.pickle as glasnostPickle
                    glasnostPickle.setParanoia(0)
                elif os.access(pickleFilePath, os.F_OK):
                    import cPickle as glasnostPickle
                    rcFile = open(pickleFilePath, 'rb')
                if rcFile is None:
                    self.fillEmptyVirtualServer(virtualServer)
                else:
                    fcntl.lockf(rcFile, fcntl.LOCK_SH)
                    version = self.readFileVersion(rcFile)
                    print 'file:',  rcFile
                    data = glasnostPickle.load(rcFile)
                    fcntl.lockf(rcFile, fcntl.LOCK_UN)
                    isReadOnly = 0
                    if not os.access(rcFile.name, os.W_OK):
                        print '\t(read-only file)'
                        isReadOnly = 1
                    rcFile.close()
                    if isinstance(data, VirtualServer):
                        virtualServer = data
                    else:
                        virtualServer.initFromOldData(data)
                    virtualServer.isReadOnly = isReadOnly
                    self.upgradeVirtualServer(virtualServer, version)
            finally:
                context.pull()
        return virtualServer

    def publicMethodWrapper(self, method, arguments, isDirectCall = 0):
        """The standard wrapper for public methods.

        Most Glasnost public methods use the same 3 parameters:
        virtualServerId, clientToken & userToken. This wrapper gets those 3
        parameters and store them in the context, so the wrapped methods don't
        have to declare these parameters.

        """

        assert len(arguments) >= 3
        virtualServerId = arguments[0]
        clientToken = arguments[1]
        userToken = arguments[2]
        arguments = arguments[3:]
        self.pushContext(virtualServerId, clientToken, userToken)
        try:
            return apply(method, arguments)
        finally:
            # Save all dirty data.
            if not isDirectCall:
                virtualServerId = context.getVar('applicationId')
                try:
                    virtualServer = self.getVirtualServer(virtualServerId)
                except faults.UnknownServerId:
                    pass
                else:
                    virtualServer.savePendingRequests()

            self.pullContext()

    def pullContext(self):
        context.pull(_level = 'pushContext')

    def pushContext(self, virtualServerId, clientToken, userToken):
        assert virtualServerId
        applicationTokens = context.getVar('applicationTokens')
        if self.hasMultipleVirtualServers:
            if not applicationTokens.has_key(virtualServerId):
                raise faults.UnknownServerId(virtualServerId)
            applicationToken = applicationTokens[virtualServerId]
        else:
            applicationToken = applicationTokens[
                    context.getVar('applicationId')]
        dispatcherId = commonTools.extractDispatcherId(virtualServerId)
        serverRole = self.applicationRole
        applicationId = commonTools.makeApplicationId(dispatcherId, serverRole)
        if clientToken is None:
            clientToken = ''
        if userToken is None:
            userToken = ''
        context.push(
                _level = 'pushContext',
                applicationId = applicationId, # = virtualServerId
                applicationToken = applicationToken,
                clientToken = clientToken,
                dispatcherId = dispatcherId,
                userToken = userToken,
                )

    def readFileVersion(self, file):
        versionLine = file.readline(80).strip()
        if not '.' in versionLine and not '_' in versionLine:
            # The first rc files versions were without version number.
            version = 0
            file.seek(0)
        elif '.' in versionLine:
            # Glasnost versions < 0.7.0.
            try:
                splittedVersion = versionLine.split('.')
                version = 0.0
                factor = 1000000.0
                for number in splittedVersion:
                    version += int(number) * factor
                    factor = factor / 1000
            except ValueError:
                # The first rc files versions were without version number.
                version = 0
                file.seek(0)
        else:
            # Glasnost versions >= 0.7.0.
            version = versionLine
            if re.match('\d\d\d\d(_\d\d\d\d)', version) is None:
                # The first rc files versions were without version number.
                version = 0
                file.seek(0)
        return version

    def registerPublicMethod(self, methodName, method = None):
        """Register a public method which is accessed through a wrapper.
        
        A public method is a method that can be called by external programs.

        See the method "publicMethodWrapper" for more informations about the
        role of the standard wrapper.

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

        *methodName*:
            Public name of the method.

        *method*:
            The public method (default = None). If None, 'methodName' is used
            to retrieve the method.
        
        """

        if method is None:
            method = getattr(self, methodName)
        self.rpcServer.add_wrappedMethod(
                methodName, method, help = method.__doc__)

    def registerPublicMethodWrapper(self, method):
        """Set the wrapper method use by public wrapped methods."""

        self.rpcServer.set_wrapper(method)

    def registerPublicMethods(self):
        self.registerPublicMethodWrapper(self.publicMethodWrapper)
        self.registerPublicMethod('addDispatcher')
        self.registerPublicMethod('canGetAdmin')

    def registerToDispatcher(self):
        # Register the server to the dispatcher.
        applicationId = context.getVar('applicationId')
        context.getVar('applicationTokens')[applicationId] = \
                getApplicationToken()
        dispatcherIds = registerServer(self.hostName, self.port)

        # Register each virtual server to the dispatcher.
        if self.hasMultipleVirtualServers:
            for dispatcherId in dispatcherIds:
                virtualServerId = commonTools.makeApplicationId(
                        dispatcherId, self.applicationRole)
                try:
                    registerVirtualServer(
                            self.hostName, self.port, virtualServerId)
                except faults.RoleNotInProfiles:
                    continue
                virtualServer = self.initVirtualServer(virtualServerId)
                self.virtualServers[virtualServerId] = virtualServer
                virtualServer = self.loadVirtualServer(virtualServer)
                self.virtualServers[virtualServerId] = virtualServer
                context.push(
                        applicationId = virtualServerId,
                        )
                context.getVar('applicationTokens')[virtualServerId] = \
                        getApplicationToken()
                context.pull()

    def removeIdsFromVirtualServer(self, dispatcherId, rolesToKeep):
        virtualServerId = commonTools.makeApplicationId(
                dispatcherId, self.applicationRole)
        if not self.virtualServers.has_key(virtualServerId):
            print 'Missing virtual server for %s' % dispatcherId
            return 2
        self.virtualServers[virtualServerId].removeIds(rolesToKeep)
        self.virtualServers[virtualServerId].markAllAsDirtyFIXME()
        return None

    def removeVirtualServerHistory(self, dispatcherId):
        return None

    def repairVirtualServer(self, virtualServer, version):
        """Upgrade old server data to the current format.

        **TO BE OVERRIDEN**
        
        """
        
        pass

    def run(self):
        signal.signal(signal.SIGINT, self.handleSignal)
        signal.signal(signal.SIGHUP, self.handleSignal)
        signal.signal(signal.SIGTERM, self.handleSignal)
        self.rpcServer.baseContext = context.get()
        while 1:
            try:
                self.rpcServer.serve_forever()
            except KeyboardInterrupt:
                break
            except:
                traceback.print_exc()
        del self.rpcServer.baseContext

    def saveVirtualServer(self, virtualServer):
        if not self.useDataFile:
            return
        if virtualServer.isReadOnly:
            return
        import cPickle as glasnostPickle
        if not os.access(virtualServer.dataDirectoryPath, os.F_OK):
            os.mkdir(virtualServer.dataDirectoryPath)
            os.chmod(virtualServer.dataDirectoryPath, 0750)
        rcFilePath = os.path.join(
                virtualServer.dataDirectoryPath,
                self.applicationName + '.pickle')
        virtualServer.lock.acquire()
        newRcFilePath = rcFilePath + '.new'
        rcFile = open(newRcFilePath, 'wb')
        os.chmod(newRcFilePath, 0640)
        rcFile.write(glasnost.fileVersionNumber + '\n')
        glasnostPickle.dump(virtualServer, rcFile, 1)
        rcFile.close()
        os.rename(newRcFilePath, rcFilePath)
        virtualServer.lock.release()
        xmlFilePath = os.path.join(
                virtualServer.dataDirectoryPath,
                self.applicationName + '.xml')
        if os.access(xmlFilePath, os.F_OK):
            os.remove(xmlFilePath)

    def startRpcServer(self):
        print 'Will listen on %s:%s' % (self.hostName, self.port)

        if self.isThreaded:
            self.rpcServer = FastThreadingServer(('', self.port))
        else:
            self.rpcServer = FastServer(('', self.port))
        self.registerPublicMethods()

        for i in range(100):
            try:
                self.registerToDispatcher()
            except faults.UnknownDispatcherInId:
                time.sleep(0.1)
                continue
            break
        else:
            raise 'unable to connect to dispatcher'

    def upgradeVirtualServer(self, virtualServer, fileVersionNumber):
        if type(fileVersionNumber) is types.IntType:
            # Upgrade pre 0.7.0 versions.
            version = fileVersionNumber
            fileVersionNumber = '0000'

            if version < 5004:
                if not hasattr(virtualServer, 'virtualServerId') \
                       or not virtualServer.virtualServerId:
                    virtualServer.virtualServerId = \
                            virtualServer.applicationId
                    del virtualServer.applicationId

            self.repairVirtualServer(virtualServer, version)

        classes = commonTools.getC3ClassLinearization(self.__class__)
        upgradeMethodNames = []
        for class_ in classes:
            for attributeName in class_.__dict__.keys():
                if attributeName.startswith('upgradeVirtualServer_'):
                    if not attributeName in upgradeMethodNames:
                        upgradeMethodNames.append(attributeName)
        upgradeMethodNames.sort()
        for upgradeMethodName in upgradeMethodNames:
            if upgradeMethodName[len('upgradeVirtualServer_'):] \
                   <= fileVersionNumber:
                continue
            getattr(self, upgradeMethodName)(virtualServer)
        virtualServer.savePendingRequests()

    def usage(self):
        print 'Usage: %s ### FIXME: To Do' % self.applicationName


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

    The product of this multiple inheritances is a fonctionnal administrable
    server.
    An administrable server uses an Admin class instance in order to provide
    administrative information (Administrators, writers, readers...).
    All methods can be overriden or extended, in fact, this class define their
    default behavior.

    Additional attribute:
    =====================

    *adminClassName*:
        The server class name. (formelly xxxServer)
    
    """

    adminClassName = None # To override.

    def canGetAdmin(self):
        if not self.useAdminReadersSet:
            return 1
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        return getProxyForServerRole('identities').setContainsUser(
                virtualServer.admin.adminsSet)

    def canModifyAdmin(self):
        """Indicate whether the caller is able to modify the admin instance.

        Only an admin can modify the admin instance.

        """

        return self.isAdmin()

    def getAdminCore(self, check = 1):
        """Get the core of the admin instance."""

        if check and not self.canGetAdmin():
            raise faults.UserAccessDenied()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        return virtualServer.admin

    def getAdminSlotValueHolderXmlRpc(self, slotPath):
        admin = self.getAdminCore()
        admin.acquireNonCore()
        try:
            slot = admin.getSlotByPath(slotPath)
            slotValueHolder = values.ValueHolder().init(
                    slot.getKind(), slot.getValue())
            result = slotValueHolder.exportToXmlRpc()
        finally:
            admin.releaseNonCore()
        return result

    def getAdminStringFromDigestXmlRpc(self, path, digest):
        path = iso8859_15(path)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        admin = self.getAdminCore()
        admin.acquireNonCore()
        try:
            try:
                part = eval(path, {'self': admin})
            except:
                raise faults.NonExistentSlotPath(path)
            result = getStringFromDigest(part, digest)
        finally:
            admin.releaseNonCore()
        if result is None:
            raise faults.UnknownStringDigest(digest)
        return utf8(result)

    def getAdminXmlRpc(self):
        """Get the admin instance."""

        admin = self.getAdminCore()
        admin.acquireNonCore()
        try:
            result = admin.exportToXmlRpc()
        finally:
            admin.releaseNonCore()
        return result

    def hasAdminSlot(self, slotPath):
        admin = self.getAdminCore()
        admin.acquireNonCore()
        try:
            return admin.hasSlotPath(slotPath)
        finally:
            admin.releaseNonCore()

    def isAdmin(self):
        """Indicate whether the caller is an administrator of this server."""
        
        virtualServerId = context.getVar('applicationId')
        dispatcherId = commonTools.extractDispatcherId(virtualServerId)
        if dispatcherId in ['glasnost://help', 'glasnost://system']:
            return 0
        virtualServer = self.getVirtualServer(virtualServerId)
        admin = virtualServer.admin
        if context.getVar('isAdmin') is not None:
            return context.getVar('isAdmin')
        result = getProxyForServerRole('identities').setContainsUser(
                admin.adminsSet)
        context.setVar('isAdmin', result)
        return result

    def modifyAdminXmlRpc(self, adminImport):
        """Modify the admin instance.
        
        Keyword argument:
        =================

        *adminImport*:
            The new admin object in XML RPC dictionnary format.
        
        Returns:
        ========

        The admin object new version attribute.

        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.

        """
        
        if not self.isAdmin():
            raise faults.UserAccessDenied()
        adminChanges = commonTools.importThing(adminImport)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        admin = virtualServer.admin
        admin.acquireNonCore()
        try:
            admin.checkModifyIsPossible(adminChanges)
            admin.modify(adminChanges)
            admin.saveNonCore()
        finally:
            admin.releaseNonCore()
        virtualServer.markAdminAsDirty(admin)
        invalidateValue('admin_%s' % self.serverRole)
        return admin.version

    def newAdmin(self):
        """Instanciate a new Admin class.
        
        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 registerPublicMethods(self):
        self.registerPublicMethod('canModifyAdmin')
        self.registerPublicMethod('getAdmin', self.getAdminXmlRpc)
        self.registerPublicMethod('getAdminSlotValueHolder',
                                  self.getAdminSlotValueHolderXmlRpc)
        self.registerPublicMethod('getAdminStringFromDigest',
                                         self.getAdminStringFromDigestXmlRpc)
        self.registerPublicMethod('hasAdminSlot')
        self.registerPublicMethod('isAdmin')
        self.registerPublicMethod('modifyAdmin', self.modifyAdminXmlRpc)
        self.registerPublicMethod('setAdminSlotValueHolder',
                                  self.setAdminSlotValueHolderXmlRpc)

    def setAdminSlotValueHolderXmlRpc(self, slotPath, valueHolderImport):
        valueHolder = commonTools.importThing(valueHolderImport)
        if not self.isAdmin():
            raise faults.UserAccessDenied()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        admin = virtualServer.admin
        admin.acquireNonCore()
        try:
            slot = admin.getSlotByPath(slotPath)
            kind = slot.getKind()
            kind.checkModelValueHolder(slot, valueHolder)
            slot.setValue(valueHolder.value)
            admin.version += 1
            admin.saveNonCore()
        finally:
            admin.releaseNonCore()
        virtualServer.markAdminAsDirty(admin)
        invalidateValue('admin_%s' % self.serverRole)

    def upgradeVirtualServer_0001_0019(self, virtualServer):
        # Fill empty adminsSet slots in admin. 
        admin = virtualServer.admin
        if admin.hasSlotName('adminsSet') and not admin.adminsSet:
            admin.adminsSet = [system.generalPublicId]
            virtualServer.markAdminAsDirty(admin)

    def upgradeVirtualServer_0001_0020(self, virtualServer):
        # Fill empty readersSet slots in admin. 
        admin = virtualServer.admin
        if admin.hasSlotName('readersSet') and not admin.readersSet:
            admin.readersSet = [system.generalPublicId]
            virtualServer.markAdminAsDirty(admin)

    def upgradeVirtualServer_0001_0028(self, virtualServer):
        # Convert user ids from "people" to "identities". 
        if virtualServer.admin.upgrade('0001_0028'):
            virtualServer.markAdminAsDirty(virtualServer.admin)

class ObjectsServer(AdministrableServerMixin, Server):
    
    """Handle the objects services.

    Attributes:
    ===========

    *VirtualServer*:        The virtual server where the object is.
        
    *dataDirectoryPath*:
        The path where the object non core datas are.

    *useAdminReadersSet*:
       Set if the object uses readers set to allow reading.
        
    *useAdminWriterSet*:
        Set if the object uses writers set to allow writing.
    
    Additional Attribute:
    ======================
    
    *objectClassName*:
        The class name of the object.
    
    """
    
    VirtualServer = ObjectsVirtualServer
    dataDirectoryPath = None
    objectClassName = None # To override.
    useAdminReadersSet = 1
    useAdminWritersSet = 1

    def addObjectXmlRpc(self, objectImport):
        """Create a new object on the server.

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

        *objectImport*:
            The new object in XML RPC dictionnary format.
        
        Returns:
        ========

        The new 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.

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

        """
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = commonTools.importThing(objectImport)

        if not self.isAdmin() and not (self.canAddObject() and (
                not object.hasSlotName('writersSet')
                or getProxyForServerRole('identities'
                        ).setContainsUser(object.getSlot('writersSet'
                                ).getValue()))):
            if not object.canBeCreatedByClient():
                raise faults.UserAccessDenied()
        object.checkAddIsPossible()
        object.setAutomaticalSlots()
        virtualServer.objects[object.id] = object
        object.saveNonCore()
        object.releaseNonCore()
        virtualServer.markObjectAsDirty(object)
        virtualServer.markCoreAsDirty()
        return object.id
    addObjectXmlRpc.allowSubscriptionAfter = 1

    def canAddObject(self):
        """Indicate whether the caller can add an object."""
         
        if self.isAdmin():
            return 1
        if not self.useAdminWritersSet:
            return 0

        admin = self.getAdminCore(check = 0)
        return getProxyForServerRole('identities').setContainsUser(
                admin.writersSet)

    def canDeleteObject(self, objectId):
        """Indicate whether the caller can delete the specified object."""
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return 0
        object = virtualServer.loadObjectCore(objectId)
        return self.isAdmin() or (
                object.hasSlotName('writersSet')
                and getProxyForServerRole('identities').setContainsUser(
                        object.getSlot('writersSet').getValue()))

    def canGetObject(self, objectId):
        """Indicate whether the caller can retrieve the specified object."""
 
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return 0
        object = virtualServer.loadObjectCore(objectId)
        if self.isAdmin():
            return 1
        try:
            if object.canBeGottenByClient():
                return 1
        except faults.UnknownServerId:
            pass
        if object.hasSlotName('readersSet'):
            readersSet = object.getSlot('readersSet').getValue()
            return getProxyForServerRole('identities').setContainsUser(
                    readersSet)
        else:
            if not self.useAdminReadersSet:
                return 1
        
            virtualServerId = context.getVar('applicationId')
            virtualServer = self.getVirtualServer(virtualServerId)

            return getProxyForServerRole('identities').setContainsUser(
                    virtualServer.admin.readersSet)

    def canGetObjects(self):
        """Indicate whether the caller can retrieve the list of objects."""
        
        if self.isAdmin():
            return 1
        if not self.useAdminReadersSet:
            return 1

        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)

        if getProxyForServerRole('identities').setContainsUser(
                virtualServer.admin.readersSet):
            return 1
        return 0

    def canModifyObject(self, objectId):
        """Indicate whether the caller can modify the specified object."""
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return 0
        object = virtualServer.loadObjectCore(objectId)
        if not object.canBeModified():
            return 0
        return self.isAdmin() or (
                object.hasSlotName('writersSet')
                and getProxyForServerRole('identities').setContainsUser(
                        object.getSlot('writersSet').getValue()))

    def canUseObject(self, objectId):
        """Indicate whether the caller can use the object.

        Using an object is not the same as viewing it (or editing it). An
        object is used in "use" mode, but read or edited in "author" mode.

        """

        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return 0
        object = virtualServer.loadObjectCore(objectId)
        if self.isAdmin():
            return 1
        try:
            if object.canBeGottenByClient():
                return 1
        except faults.UnknownServerId:
            pass
        if object.hasSlotName('usersSet'):
            usersSet = object.getSlot('usersSet').getValue()
            return not usersSet or getProxyForServerRole(
                    'identities').setContainsUser(usersSet)
        else:
            return 1

    def deleteObject(self, objectId):
        """Delete a specified object.
        
        Keyword argument:
        =================

        *objectId*:
            The ID of the object to delete.

        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 serious!)
            - The adminImport __thingName__ key is not a valid Thing name.

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

        """
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        if not self.canDeleteObject(objectId) \
                and not object.canBeDeletedByClient():
            raise faults.UserAccessDenied()
        object.removeNonCore()
        object.clear()
        del virtualServer.objects[objectId]
        virtualServer.markObjectAsDeleted(objectId)
        virtualServer.markCoreAsDirty()
        invalidateValue(objectId)

    def getIdsAllowedToDeleteObject(self, objectId):
        
        allowedObjectIds = []
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return []
        object = virtualServer.loadObjectCore(objectId)
        
        admin = self.getAdminCore()
        admin.acquireNonCore()
        adminsObjectIds = admin.adminsSet
        
        if adminsObjectIds and type(adminsObjectIds) is type([]):
            for adminObjectId in adminsObjectIds:
                allowedObjectIds += getSetContainedIds([adminObjectId])
        
        if object.hasSlotName('writersSet'):
            adminsObjectIds = object.getSlot('writersSet').getValue()
            if adminsObjectIds and type(adminsObjectIds) is type([]):
                for adminObjectId in adminsObjectIds:
                    allowedObjectIds += getSetContainedIds([adminObjectId])
        
        d = {}
        for k in allowedObjectIds:
            d[k] = 0
        return d.keys()
        
    
    def getIdsAllowedToModifyObject(self, objectId):
        
        allowedObjectIds = []
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return []
        object = virtualServer.loadObjectCore(objectId)
        if not object.canBeModified():
            return []
        admin = self.getAdminCore()
        admin.acquireNonCore()
        adminsObjectIds = admin.adminsSet
        
        if adminsObjectIds and type(adminsObjectIds) is type([]):
            for adminObjectId in adminsObjectIds:
                allowedObjectIds += getSetContainedIds([adminObjectId])
        
        if object.hasSlotName('writersSet'):
            adminsObjectIds = object.getSlot('writersSet').getValue()
            if adminsObjectIds and type(adminsObjectIds) is type([]):
                for adminObjectId in adminsObjectIds:
                    allowedObjectIds += getSetContainedIds([adminObjectId])
             
        d = {}
        for k in allowedObjectIds:
            d[k] = 0
        return d.keys()
 
    def getLastObjectIds(self, objectsCount, possibleReadersSet,
                         possibleWritersSet):
        """Retrieve the last accessed objects ids.

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

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

        *possibleReadersSet*:
            FIXME

        *possibleWriterSet*:
            FIXME

        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.

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

        """
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        isAdmin = self.isAdmin()
        try:
            possibleReaderIds = getSetContainedIds(
                    possibleReadersSet, ['identities'],
                    raiseWhenUncountable = 1)
        except faults.UncountableGroup:
            possibleReaderIds = 'everybody'
        try:
            possibleWriterIds = getSetContainedIds(
                    possibleWritersSet, ['identities'],
                    raiseWhenUncountable = 1)
        except faults.UncountableGroup:
            possibleWriterIds = 'everybody'
        objectIds = virtualServer.objects.keys()
        if objectIds and hasattr(
            virtualServer.loadObjectCore(objectIds[0]), 'modificationTime'):
            def modificationTimeSorter(
                xId, yId, virtualServer = virtualServer):
                return cmp(virtualServer.loadObjectCore(yId).modificationTime,
                           virtualServer.loadObjectCore(xId).modificationTime)
            objectIds.sort(modificationTimeSorter)
        result = []
        for objectId in objectIds:
            object = virtualServer.loadObjectCore(objectId)
            if object.hasSlotName('readersSet'):
                hasReadersSet = 1
                readersSet = object.getSlot('readersSet').getValue()
            else:
                hasReadersSet = 0
                readersSet = None
            if not isAdmin and hasReadersSet and not getProxyForServerRole(
                    'identities').setContainsUser(readersSet):
                continue
            if not self.getLastObjectIds_filter(
                    possibleReaderIds, hasReadersSet, readersSet):
                continue

            if object.hasSlotName('writersSet'):
                hasWritersSet = 1
                writersSet = object.getSlot('writersSet').getValue()
            else:
                hasWritersSet = 0
                writersSet = None
            if not self.getLastObjectIds_filter(
                    possibleWriterIds, hasWritersSet, writersSet):
                continue

            result.append(objectId)
            if objectsCount != -1 and len(result) >= objectsCount:
                break
        return result

    def getLastObjectIds_filter(self, possibleIds, hasSet, set):
        if hasSet:
            try:
                ids = getSetContainedIds(set, ['identities', 'people'],
                                         raiseWhenUncountable = 1)
            except faults.IllegalRecursiveGroup:
                # There is an error in the group. For security reasons, return
                # false.
                ids = []
            except faults.UserAccessDenied:
                ids = []
            except faults.UncountableGroup:
                ids = 'everybody'
        else:
            ids = 'everybody'
        if possibleIds == 'everybody' and ids == 'everybody':
            return 1
        elif possibleIds == 'everybody':
            return len(ids) > 0
        elif ids ==  'everybody':
            return len(possibleIds) > 0
        else:
            for possibleId in possibleIds:
                if possibleId in ids:
                    return 1
            return 0

    def getNextId(self):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        virtualServer.lock.acquire()
        objectId = virtualServer.getNextLocalId()
        virtualServer.lock.release()
        return '%s/%s' % (virtualServerId, objectId)

    # The real XML-RPC function is getObjectXmlRpc.
    def getObjectCore(self, objectId):
        """Get the object core datas.

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

        *objectId*:
            The object ID string.
        
        Return the object instance.

        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.

        """
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        clientToken = context.getVar('clientToken')
        userToken = context.getVar('userToken')
        if not self.canGetObject(objectId):
            raise faults.UserAccessDenied()
        return object

    def getObjectIds(self):
        """Retrieve the ids of all the objects stored on the server.

        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.

        """
        
        if not self.canGetObjects():
            raise faults.UserAccessDenied()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        result = []
        for objectId, objectCore in virtualServer.objects.items():
            if self.canGetObject(objectId) \
                    or objectCore.canBeGottenByClient():
                result.append(objectId)
        return result

    def getObjectIdsWithCriteria(self, criteria):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        result = []
        for objectId, objectCore in virtualServer.objects.items():
            if not self.canGetObject(objectId):
                continue
            try:
                if eval(criteria, {'object': objectCore,
                                   'result': result}, {}):
                    result.append(objectId)
            except Exception, e:
                pass
        return result
        

    def getObjectLabelAndLanguageXmlRpc(self, objectId):
        """Retrieve the object label and language in sequence.

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

        *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.

        """
        
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        result = [utf8(object.getLabel()), object.getLabelLanguage()]
        return result

    def getObjectsCount(self):
        if not self.isAdmin():
            raise faults.UserAccessDenied()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        return len(virtualServer.objects)

    def getObjectSlotValueHolderXmlRpc(self, objectId, slotPath):
        object = self.getObjectCore(objectId)
        object.acquireNonCore()
        try:
            slot = object.getSlotByPath(slotPath)
            slotValueHolder = values.ValueHolder().init(
                    slot.getKind(), slot.getValue())
            result = slotValueHolder.exportToXmlRpc()
        finally:
            object.releaseNonCore()
        return result

    def getObjectStringFromDigestXmlRpc(self, objectId, path, digest):
        """Retrieve a string in the specified object from its MD5 digest.
        
        Keyword arguments:
        ==================

        *path*:
            The path to the string location in the object. It could be the
            object itself, or a dictionnary or a sequence, etc.

        *digest*:
            The MD5 digest of the wanted string.

        Return value:
        =============
        
        *The the object string representation in UTF-8*

        """

        path = iso8859_15(path)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        object.acquireNonCore()
        try:
            # The objectLabel is accessible from everybody.
            if path == 'self.getLabel()':
                result = getStringFromDigest(object.getLabel(), digest)
            else:
                if not self.canGetObject(objectId):
                    raise faults.UserAccessDenied()
                try:
                    part = eval(path, {'self': object})
                except:
                    raise faults.NonExistentSlotPath(path)
                result = getStringFromDigest(part, digest)
        finally:
            object.releaseNonCore()
        if result is None:
            raise faults.UnknownStringDigest(digest)
        return utf8(result)

    def getObjectXmlRpc(self, objectId):
        """Retrieve a specified object instance.

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

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

        Returns:
        ========

        *Object instance XML RPC dictionnary definition*

        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.

        """
        object = self.getObjectCore(objectId)
        object.acquireNonCore()
        try:
            result = object.exportToXmlRpc()
        finally:
            object.releaseNonCore()
        return result

    def getPartialObjectXmlRpc(self, objectId, requiredSlotNames):
        """Retrieve some slots of an object.

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

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

        *requiredSlotNames*:
            The wanted slot names sequence. If empty the whole instance is
            retrieved.
        
        Exceptions:
        ===========

        *AssertionError*:
            - 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.getObjectXmlRpc(objectId)
        object = self.getObjectCore(objectId)
        useNonCore = 0
        for slotName in ['id', 'language', 'readersSet', 'version']:
            if not slotName in requiredSlotNames \
                   and object.hasSlotName(slotName):
                slot = object.getSlot(slotName)
                if slot.getKind().isExportable():
                    requiredSlotNames.append(slotName)
        for slotName in object.getSlotNames():
            if not slotName in requiredSlotNames:
                continue
            slot = object.getSlot(slotName)
            kind = slot.getKind()
            if not kind.isInCore(slot):
                useNonCore = 1
                break
        if useNonCore:
            object.acquireNonCore()
        try:
            result = object.exportToXmlRpc(
                requiredSlotNames = requiredSlotNames)
        finally:
            if useNonCore:
                object.releaseNonCore()
        return result

    def getObjectWithVersionXmlRpc(self, objectId, versionNumber):
        if not self.canGetObject(objectId):
            raise faults.UserAccessDenied()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.getObjectWithVersion(objectId, versionNumber)
        if not object:
            raise faults.UnknownObjectVersion(objectId, versionNumber)
        return object.exportToXmlRpc()

    def getRevisionsInfos(self, objectId):
        if not self.canGetObject(objectId):
            raise faults.UserAccessDenied()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        return virtualServer.getRevisionsInfos(objectId)

    def hasObject(self, objectId):
        """Indicate whether the server contains an object.

        Keyword argument:
        =================
        
        *objectId*:
            The ID of the object to look up.

        """

        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        return virtualServer.canLoadObjectCore(objectId)

    def hasObjectSlot(self, objectId, slotPath):
        object = self.getObjectCore(objectId)
        object.acquireNonCore()
        try:
            return object.hasSlotPath(slotPath)
        finally:
            object.releaseNonCore()

    def modifyObjectXmlRpc(self, objectImport):
        """Modify an object.
        
        Keyword argument:
        =================

        *objectImport*:
            The modified object in XML RPC dictionnary format.
        
        Returns:
        ========

        The new version number of the modified object.

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

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

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

        *faults.ReadOnlyObject*:
            The object is read only.
        
        """
        
        objectChanges = commonTools.importThing(objectImport)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectChanges.id)
        object.acquireNonCore()
        try:
            if not object.canBeModified():
                raise faults.ReadOnlyObject()
            if not self.canModifyObject(object.id) or not (
                    self.isAdmin()
                    or not objectChanges.hasSlotName('writersSet')
                    or getProxyForServerRole('identities').setContainsUser(
                    objectChanges.getSlot('writersSet').getValue())):
                if not object.canBeModifiedByClient():
                    raise faults.UserAccessDenied()
            object.checkModifyIsPossible(objectChanges)
            object.modify(objectChanges)
            object.saveNonCore()
        finally:
            object.releaseNonCore()
        virtualServer.markObjectAsDirty(object)
        invalidateValue(object.id)
        return object.version

    def modifyPartialObjectXmlRpc(self, objectImport, givenSlotNames):
        """Modify some slots of an object.
        
        Keyword arguments:
        ==================

        *objectImport*:
            The slots to modify, stored in an incomplete object in XML RPC
            dictionnary format.

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

        The new version number of the modified object.

        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.

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

        """
        
        if not givenSlotNames:
            return self.modifyObjectXmlRpc(objectImport)
        if not objectImport.has_key('id'):
            raise faults.MissingId()
        id = objectImport['id']
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(id)
        object.acquireNonCore()
        try:
            if not object.canBeModified():
                raise faults.ReadOnlyObject()

            objectChanges = commonTools.newThing(
                    'object', objectImport['__thingName__'])
            if not 'id' in givenSlotNames:
                givenSlotNames.append('id')
            if not 'version' in givenSlotNames:
                givenSlotNames.append('version')
            object.initPartial(objectChanges, givenSlotNames = givenSlotNames)
            objectChanges.importFromXmlRpc(objectImport)

            if not self.canModifyObject(object.id) or not (
                    self.isAdmin()
                    or not objectChanges.hasSlotName('writersSet')
                    or getProxyForServerRole('identities').setContainsUser(
                    objectChanges.getSlot('writersSet').getValue())):
                if not object.canBeModifiedByClient():
                    raise faults.UserAccessDenied()
            object.checkModifyIsPossible(
                objectChanges, givenSlotNames = givenSlotNames)
            object.modify(
                objectChanges, givenSlotNames = givenSlotNames)
            object.saveNonCore()
        finally:
            object.releaseNonCore()
        virtualServer.markObjectAsDirty(object)
        invalidateValue(object.id)
        return object.version

    def newObject(self):
        """Instanciate a new object.

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

        *fields*:
            Useless.

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

    def registerPublicMethods(self):
        Server.registerPublicMethods(self)
        AdministrableServerMixin.registerPublicMethods(self)
        self.registerPublicMethod('addObject', self.addObjectXmlRpc)
        self.registerPublicMethod('canAddObject')
        self.registerPublicMethod('canDeleteObject')
        self.registerPublicMethod('canGetObject')
        self.registerPublicMethod('canGetObjects')
        self.registerPublicMethod('canModifyObject')
        self.registerPublicMethod('canUseObject')
        self.registerPublicMethod('deleteObject')
        self.registerPublicMethod('getIdsAllowedToModifyObject')
        self.registerPublicMethod('getIdsAllowedToDeleteObject')
        self.registerPublicMethod('getLastObjectIds')
        self.registerPublicMethod('getObject', self.getObjectXmlRpc)
        self.registerPublicMethod('getObjectIds')
        self.registerPublicMethod('getObjectIdsWithCriteria')
        self.registerPublicMethod('getObjectLabelAndLanguage',
                                         self.getObjectLabelAndLanguageXmlRpc)
        self.registerPublicMethod('getObjectsCount')
        self.registerPublicMethod('getObjectSlotValueHolder',
                                         self.getObjectSlotValueHolderXmlRpc)
        self.registerPublicMethod('getObjectStringFromDigest',
                                         self.getObjectStringFromDigestXmlRpc)
        self.registerPublicMethod('getPartialObject',
                                         self.getPartialObjectXmlRpc)
        # FIXME: those two should only be registered if object supports
        # versioning
        self.registerPublicMethod('getObjectWithVersion',
                                        self.getObjectWithVersionXmlRpc)
        self.registerPublicMethod('getRevisionsInfos')
        self.registerPublicMethod('hasObject')
        self.registerPublicMethod('hasObjectSlot')
        self.registerPublicMethod('modifyObject',
                                         self.modifyObjectXmlRpc)
        self.registerPublicMethod('modifyPartialObject',
                                         self.modifyPartialObjectXmlRpc)
        self.registerPublicMethod('searchObjectIds')
        self.registerPublicMethod('setObjectSlotValueHolder',
                                         self.setObjectSlotValueHolderXmlRpc)
        self.registerPublicMethod('sortObjectIds')
        self.registerPublicMethod('subscribe')
        self.registerPublicMethod('unsubscribe')

    def searchObjectIds(self, objectIds, sortRule, searchDict):
        """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
        
        + 'attribute_name_PT = value' means plain text case insensitive
                                       search for value in attribute
                                             
        Logic
        =====
        
        keyword1 = value1, keyword2 = value2 are processed with 'and'
        except for plain text search keywords, which are seen as with an
        'or' between them
        
        When you have both keyword sorts, they are processed with 'or'
        between plain text search keywords, and after that with an 'and'
        between the plain text search result and the remaining search keywords

        Apart from that, one search keyword can have a list as search value.
        If so, search value is splitted into words, and each word is searched
        in the corresponding attribute value, so that every one must be found.
        
        Keyword arguments:
        ==================

        *objectIds*:
            A sequence of ids which if it is not empty, is used to reduce the
            area of search.
        
        *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.

        *standard Exception*:
            - Invalid syntax in the *searchDict* string.
              
        *KeyError*:
            The virtual server ID does not correspond to a instanciated
            virtual server.
 
        """
        
        if not self.canGetObjects():
            raise faults.UserAccessDenied()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        searchSlotNames = []
        searchOperators = []
        searchValues = []
        def testType(value, operator):
            return (operator in ['EQ', 'LT', 'GT', 'LE', 'GE'] and \
                   type(value) in (type(''), type(0), type(0.0)) or \
                   operator == 'CT' or \
                   operator == 'IN' and type(value) is type([]) or \
                   operator == 'PT' and \
                    type(value) in (type(''), type([])))
        object = self.newObject()
        allSlotNames = object.getSlotNames()
        for searchKeyword in searchDict.keys():
            slotName = searchKeyword[:-3]
            searchOperator = searchKeyword[-2:]
            searchValue = searchDict[searchKeyword]
            if not slotName in allSlotNames \
                   or not testType(searchValue, searchOperator):
                raise faults.InvalidSearchRequest(searchKeyword, searchValue)
            searchSlotNames.append(slotName)
            searchOperators.append(searchOperator)
            searchValues.append(searchValue) 
        useNonCore = 0
        for slotName in searchSlotNames:
            slot = object.getSlot(slotName)
            kind = slot.getKind()
            if not kind.isInCore(slot):
                useNonCore = 1
                break
        result = []
        def plainTextSearch(stringToDig, searchValue):
            # FIXME this place is for a better plain text search
            searchedString = stringToDig.lower()
            for term in searchValue:
                term = iso8859_15(term.lower())
                if searchedString.find(term) == -1:
                    return 0
            return 1
        def test(searchValue, testValue, operator):
            return (operator == 'EQ' and testValue == searchValue) or \
                   (operator == 'LT' and testValue < searchValue) or \
                   (operator == 'GT' and testValue > searchValue) or \
                   (operator == 'LE' and testValue <= searchValue) or \
                   (operator == 'GE' and testValue >= searchValue) or \
                   (operator == 'CT' and searchValue in testValue) or \
                   (operator == 'IN' and testValue in searchValue)
        if not objectIds:
            itemsToTest = virtualServer.objects.items()
        else:
            itemsToTest = []
            for objectId, object in virtualServer.objects.items():
                if not objectId in objectIds:
                    continue
                itemsToTest.append((objectId, object))
        for objectId, object in itemsToTest:
            if self.canGetObject(objectId) or object.canBeGottenByClient():
                if useNonCore:
                    # FIXME: no call to releaseNonCore
                    object.acquireNonCore()
                kept = 1
                plainTextSearchOk = 0
                for i in range(len(searchSlotNames)):
                    slotName = searchSlotNames[i]
                    searchOperator = searchOperators[i]
                    searchValue = searchValues[i]
                    slot = object.getSlot(slotName)
                    testValue = slot.getValue()
                    if not testValue:
                        continue
                    if searchOperator == 'PT':
                        if not plainTextSearchOk \
                               and plainTextSearch(testValue, searchValue):
                            plainTextSearchOk = 1
                        continue
                    if searchOperator in ['EQ', 'LT', 'LE', 'GT', 'GE', 'CT',
                                          'IN'] and \
                           not test(searchValue, testValue, searchOperator):
                        kept = 0
                        break
                if kept and (plainTextSearchOk or \
                        not 'PT' in searchOperators):
                    result.append(objectId)
        if result:
            result = self.sortObjectIds(result, sortRule)
        return result

    def setObjectSlotValueHolderXmlRpc(self, objectId, slotPath,
                                       valueHolderImport):
        valueHolder = commonTools.importThing(valueHolderImport)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        object.acquireNonCore()
        try:
            if not object.canBeModified():
                raise faults.ReadOnlyObject()
            if not self.canModifyObject(object.id):
                if not object.canBeModifiedByClient():
                    raise faults.UserAccessDenied()
            slot = object.getSlotByPath(slotPath)
            kind = slot.getKind()
            kind.checkModelValueHolder(slot, valueHolder)
            slot.setValue(valueHolder.value)
            object.version += 1
            object.saveNonCore()
        finally:
            object.releaseNonCore()
        virtualServer.markObjectAsDirty(object)
        invalidateValue(object.id)

    def sortObjectIds(self, objectIds, sortRule): 
        """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.

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

        """

        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not objectIds:
            objectIds = virtualServer.objects.keys()
        else:
            objectIds = [objectId
                         for objectId in objectIds
                         if virtualServer.canLoadObjectCore(objectId)]
        def straightIdSorter(xId, yId):
            try:
                xParts, yParts = xId.split('/'), yId.split('/')
                return cmp(xParts[-2], yParts[-2]) or \
                       cmp(int(xParts[-1]), int(yParts[-1]))
            except: # TODO: tighter check
                raise Exception(
                    'Unable to compare what looks like 2 invalid Glasnost'
                    ' identifiers')
        def straightLabelSorter(xId, yId, virtualServer = virtualServer):
            return cmp(virtualServer.loadObjectCore(xId).getLabel(),
                       virtualServer.loadObjectCore(yId).getLabel())
        if sortRule == 'id':
            try:
                objectIds.sort(straightIdSorter)
            except: # TODO: tighter check
                raise Exception('Unable to compare objects ids')
        elif sortRule == 'modificationTime':
            try:
                objects = {}
                for objectId in objectIds:
                    objects[objectId] = virtualServer.loadObjectCore(objectId)
                slot = objects.values()[0].getSlot('modificationTime')
                kind = slot.getKind()
                if not kind.isInCore(slot):
                    map (lambda x:x.acquireNonCore(), objects.values())
                def reverseModificationTimeSorter(
                        xId, yId, objects = objects):
                    return cmp(
                        objects[yId].modificationTime,
                        objects[xId].modificationTime)
                objectIds.sort(reverseModificationTimeSorter)
            except: # TODO: tighter check
                raise Exception(
                    'Unable to compare objects modification time')
        elif sortRule == 'date':
            try:
                objects = {}
                for objectId in objectIds:
                    objects[objectId] = virtualServer.loadObjectCore(objectId)
                slot = objects.values()[0].getSlot('date')
                kind = slot.getKind()
                if not kind.isInCore(slot):
                    # FIXME: no call to releaseNonCore
                    map (lambda x:x.acquireNonCore(), objects.values())
                def reverseDateSorter(
                    xId, yId, objects = objects):
                    return cmp(
                        objects[yId].date, objects[xId].date)
                objectIds.sort(reverseDateSorter)
            except: # TODO: tighter check
                raise Exception('Unable to compare objects date')
        else:
            objectIds.sort(straightLabelSorter)
        return objectIds

    def subscribe(self, method, when, callBackName):
        if not self.rpcServer._methods.has_key(method) \
           and not method == '_ALL_':
            raise Exception('No such method')
        if not when in ('before', 'after'):
            raise Exception('Wrong value for "when" (= %s)' % when)

        if not self.isAdmin():
            raise faults.UserAccessDenied()

        clientId = getApplicationId(clientToken)

        virtualServerId = context.getVar('applicationId')
        self.rpcServer.add_subscription(
                when, virtualServerId, method, clientId, callBackName)

    def unsubscribe(self, method, when, callBackName):
        if not self.isAdmin():
            raise faults.UserAccessDenied()

        clientId = getApplicationId(clientToken)

        virtualServerId = context.getVar('applicationId')
        self.rpcServer.remove_subscription(
                when, virtualServerId, method, clientId, callBackName)

    def upgradeVirtualServer_0001_0019(self, virtualServer):
        AdministrableServerMixin.upgradeVirtualServer_0001_0019(
                self, virtualServer)

        # In each object, fill the readersSet slot when it exists and is empty.
        for id, object in virtualServer.objects.items():
            if object.hasSlotName('readersSet') and not object.readersSet:
                object.readersSet = [system.generalPublicId]
                virtualServer.markObjectAsDirty(object)

    def upgradeVirtualServer_0001_0028(self, virtualServer):
        AdministrableServerMixin.upgradeVirtualServer_0001_0028(
                self, virtualServer)

        # Convert user ids from "people" to "identities". 
        for object in virtualServer.objects.values():
            if object.upgrade('0001_0028'):
                virtualServer.markObjectAsDirty(object)

