#!/usr/bin/env python
# -*- 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 Dispatcher"""

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


import os
import sgmllib
import sys
import whrandom

glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
sys.path.insert(0, glasnostPythonDir)

import glasnost

import glasnost.common.faults as faults
import glasnost.common.tools_new as commonTools

from glasnost.server.ObjectsServer import Server, VirtualServer
from glasnost.server.tools import *

from glasnost.proxy.DispatcherProxy import callServer, getApplicationToken


applicationName = 'Dispatcher'
applicationRole = None


class ProfileRolesParser(sgmllib.SGMLParser):
    inRole = 0
    roleName = None
    roles = None
    def __init__(self, body):
        sgmllib.SGMLParser.__init__(self)
        self.roles = []
        self.feed(body)

    def start_role(self, attrs):
        self.inRole = 1
        self.roleName = ''

    def end_role(self):
        self.inRole = 0
        self.roles.append(self.roleName)
        self.roleName = None

    def handle_data(self, data):
        if self.inRole:
            self.roleName += data


class DispatcherVirtualServer(VirtualServer):
    """Partial class designed for aggregation.
    
    The aggregation of this class in the Dispatcher is used in order to provide
    special virtual server, a dispatcher virtual server.
        
    Attributes:
    ===========

    *applicationTokens*:
        The dictionnary containing all the Glasnost application token. The
        key is the client token.
    
    *serverAccessors*:
        The servers access informations dictionnary, sorted by applicationId.
        It consists of:
           
        serverHostName:
            The server IP adress or hostname.
            
        serverPort:
            The port where request will be sent.
             
        isGateway:
            If true (or 1), the request are sent through the Glasnost gateway.

    *serverInfos*:
        The role and accessor of each server, sorted by "hostName:port".

    *virtualServerIds*:
        The dictionnary containing all the Glasnost servers ID string
        (including virtual servers) registered to this Dispatcher instance. The
        key is the application Token, the value is the virtual server ID
        string.
            
    """
    
    applicationTokens = None
    dispatcherIds = None
    serverAccessors = None
    defaultAccessors = None
    serverInfos = None
    virtualServerIds = None
    virtualServerIdProfiles = None

    def init(self):
        VirtualServer.init(self)
        self.applicationTokens = {}
        self.defaultAccessors = {}
        self.dispatcherIds = ['glasnost://system', 'glasnost://help']
        self.serverAccessors = {}
        self.serverInfos = {}
        self.virtualServerIds = {}
        self.virtualServerIdProfiles = {
                'glasnost://help': ['basic', 'cms', 'translations'],
                'glasnost://system': ['basic', 'cms', 'translations'],
                }


class Dispatcher(Server):
    VirtualServer = DispatcherVirtualServer
    hasMultipleVirtualServers = 0
    randomGenerator = None
    useDataFile = 0
    rolesByProfile = None

    def getApplicationId(self, applicationToken):
        """Get the application ID from the application token.
        
        Keyword arguments:
        ==================

        *applicationToken*:
            The valid application token.

        Return the application ID string.

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

        *faults.UnknownApplicationToken*:
            The given application token is not in the dispatcher virtual server
            ids dictionnary.
            
        """
        
        virtualServer = self.getVirtualServer(None)
        if not virtualServer.virtualServerIds.has_key(applicationToken):
            raise faults.UnknownApplicationToken(applicationToken)
        return virtualServer.virtualServerIds[applicationToken]

    def getApplicationToken(self, clientId):
        virtualServer = self.getVirtualServer(None)
        virtualServer.lock.acquire()
        if virtualServer.applicationTokens.has_key(clientId):
            clientToken = virtualServer.applicationTokens[clientId]
            virtualServer.lock.release()
        else:
            while 1:
                clientToken = str(self.randomGenerator.uniform(0.1, 1))[2:]
                if not virtualServer.virtualServerIds.has_key(clientToken):
                    break
            virtualServer.applicationTokens[clientId] = clientToken
            virtualServer.virtualServerIds[clientToken] = clientId
            virtualServer.lock.release()
            virtualServer.markCoreAsDirty()
        return clientToken

    def getRegisteredRoles(self):
        virtualServer = self.getVirtualServer(None)
        dispatcherId = context.getVar('applicationId')
        # Note: in the next lines, the dispatcher role ('') is removed from the
        # list of roles.
        if virtualServer.serverAccessors.has_key(dispatcherId):
            roles = []
            for serverInfo in virtualServer.serverInfos.values():
                if not serverInfo['role'] or serverInfo['role'] in roles:
                    continue
                if virtualServer.serverAccessors.has_key(
                        commonTools.makeApplicationId(
                            dispatcherId, serverInfo['role'])):
                    roles.append(serverInfo['role'])
            return roles
        else:
            # Use the roles of the default accessors.
            return [serverInfo['role']
                    for serverInfo in virtualServer.serverInfos.values()
                    if serverInfo['role']]

    def getServerAccessor(self, serverId):
        """Return the server access information dictionnary.
        
        
        Keyword argument:
        =================

        *serverId*:
            The server ID string to access to.

        Return the server access information dictionnary.
        
        *Exceptions*:
        =============

        *faults.UnknownServerId*:
            The server ID does not exists in the server dictionnary of server
            access informations dictionnary.
        
        """
        
        virtualServer = self.getVirtualServer(None)
        if not virtualServer.serverAccessors.has_key(serverId):
            serverDispatcherId = commonTools.extractDispatcherId(serverId)
            if serverDispatcherId != context.getVar('dispatcherId'):
                raise faults.UnknownDispatcherInId(serverId)
            serverRole = commonTools.extractRole(serverId)
            if not virtualServer.defaultAccessors.has_key(serverRole):
                raise faults.UnknownServerId(serverId)
            serverAccessor = virtualServer.defaultAccessors[serverRole]
            if serverDispatcherId != self.dispatcherId:
                serverAccessor = serverAccessor.copy()
                serverAccessor['isDefault'] = 1
            return serverAccessor
        return virtualServer.serverAccessors[serverId]

    def init(self):
        self.randomGenerator = whrandom.whrandom()
        self.initProfiles()
        Server.init(self)

    def initProfiles(self):
        self.rolesByProfile = {}
        profilesPath = os.path.join(commonTools.configDir, 'profiles')
        profiles = os.listdir(profilesPath)
        values = [x[:-4] for x in profiles if x.endswith('.xml')]
        for p in values:
            pFileName = os.path.join(profilesPath, p + '.xml')
            self.rolesByProfile[p] = ProfileRolesParser(
                    open(pFileName).read()).roles

    def loadConfigOptions(self):
        Server.loadConfigOptions(self)

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

        autorizedHostNames = commonTools.getConfig(
                'Security', 'RestrictRegisterTo', default = '')
        autorizedHostNames = autorizedHostNames.replace(',', ' ')
        autorizedHostNames = autorizedHostNames.strip().split()
        configContext.setVar('autorizedHostNames', autorizedHostNames)

    def registerDispatcherId(self, dispatcherId, profiles):
        """Register a new dispatcherId to the dispatcher."""
        
        virtualServer = self.getVirtualServer(None)

        # Ensure that dispatcherId has a valid syntax.
        dispatcherId = commonTools.extractDispatcherId(dispatcherId)

        if dispatcherId in virtualServer.dispatcherIds:
            return

        clientToken = context.getVar('clientToken')
        if not virtualServer.virtualServerIds.has_key(clientToken):
            raise faults.UnknownApplicationToken(clientToken)
        clientId = virtualServer.virtualServerIds[clientToken]

        virtualServer.dispatcherIds.append(dispatcherId)
        virtualServer.virtualServerIdProfiles[dispatcherId] = profiles
        print 'Registered dispatcher id "%s"' % dispatcherId

        if virtualServer.serverAccessors.has_key(clientId):
            clientAccessor = virtualServer.serverAccessors[clientId]
        else:
            clientAccessor = virtualServer.defaultAccessors[
                    commonTools.extractRole(clientId)]
        for serverInfo in virtualServer.serverInfos.values():
            serverAccessor = serverInfo['accessor']
            if serverAccessor == clientAccessor:
                # We must avoid to call the VirtualHosts server back, because
                # when it calls registerDispatcherId at initialization, it is
                # not yet ready to receive public calls (serve_forever has not
                # yet been called).
                continue
            serverId = commonTools.makeApplicationId(
                    'glasnost://%s' % serverAccessor['serverHostName'],
                    serverInfo['role'])

            roles = {'':1} # '' is for dispatcher
            for p in virtualServer.virtualServerIdProfiles[dispatcherId]:
                for r in self.rolesByProfile[p]:
                    roles[r] = 1
            rolesForVirtualServer = roles.keys()

            if serverInfo['role'] in rolesForVirtualServer:
                print 'Calling addDispatcher(%s) for %s' % (
                        dispatcherId, serverId)
                context.push(_level = 'registerDispatcherId',
                             directServerAccessor = serverAccessor)
                try:
                    callServer(serverId, 'addDispatcher', [
                            serverId, getApplicationToken(),
                            context.getVar('userToken'), dispatcherId])
                finally:
                    context.pull(_level = 'registerDispatcherId')
                print 'Called addDispatcher(%s) for %s' % (dispatcherId, serverId)
            else:
                print 'Not calling addDispatcher(%s) for %s' % (
                        dispatcherId, serverId)

    def registerToDispatcher(self):
        applicationId = context.getVar('applicationId')
        applicationToken = self.getApplicationToken(applicationId)
        context.getVar('applicationTokens')[applicationId] = applicationToken
        context.push(clientToken = applicationToken)
        self.registerServer(self.hostName, self.port)

        virtualServer = self.getVirtualServer(None)
        for dispatcherId in virtualServer.dispatcherIds:
            applicationToken = self.getApplicationToken(dispatcherId)
            context.getVar('applicationTokens')[dispatcherId] \
                    = applicationToken
            self.registerVirtualServer(self.hostName, self.port, dispatcherId)
        context.pull()

    def registerPublicMethods(self):
        Server.registerPublicMethods(self)
        self.registerPublicMethod('getApplicationId')
        self.registerPublicMethod('getApplicationToken')
        self.registerPublicMethod('getRegisteredRoles')
        self.registerPublicMethod('getServerAccessor')
        self.registerPublicMethod('registerDispatcherId')
        self.registerPublicMethod('registerServer')
        self.registerPublicMethod('registerVirtualServer')
        self.registerPublicMethod('unregisterServer')
        self.registerPublicMethod('unregisterVirtualServer')
        self.registerPublicMethod('updateDispatcherIdProfiles')

    def registerServer(self, serverHostName, serverPort):
        virtualServer = self.getVirtualServer(None)
        clientToken = context.getVar('clientToken')
        if not virtualServer.virtualServerIds.has_key(clientToken):
            raise faults.UnknownApplicationToken(clientToken)
        clientId = virtualServer.virtualServerIds[clientToken]
        clientRole = commonTools.extractRole(clientId)

        serverNameAndPort = '%s:%s' % (serverHostName, serverPort)
        autorizedHostNames = context.getVar('autorizedHostNames')
        if autorizedHostNames and not serverHostName in autorizedHostNames:
            raise faults.ServerRegistrationDenied(
                    serverHostName, serverPort)

        accessor = {
                'serverHostName': serverHostName,
                'serverPort': serverPort,
                }
        virtualServer.serverInfos[serverNameAndPort] = {
                'accessor': accessor,
                'role': clientRole,
                }

        virtualServer.defaultAccessors[clientRole] = accessor
        virtualServer.markCoreAsDirty()
        print 'Registered server %s (role = %s)' % (
                serverNameAndPort, clientRole)

        return virtualServer.dispatcherIds

    def registerVirtualServer(self, serverHostName, serverPort, serverId):
        virtualServer = self.getVirtualServer(None)
        # Ensure that serverId has a valid syntax.
        serverId = commonTools.extractServerId(serverId)
        clientToken = context.getVar('clientToken')
        if not virtualServer.virtualServerIds.has_key(clientToken):
            raise faults.UnknownApplicationToken(clientToken)

        serverDispatcherId = commonTools.extractDispatcherId(serverId)
        if not serverDispatcherId in virtualServer.dispatcherIds:
            raise faults.UnknownDispatcherInId(serverId)
            
        serverNameAndPort = '%s:%s' % (serverHostName, serverPort)
        if not virtualServer.serverInfos.has_key(serverNameAndPort):
            raise faults.UnregisteredServer(serverHostName, serverPort)

        roles = {'':1} # '' is for dispatcher
        for p in virtualServer.virtualServerIdProfiles[serverDispatcherId]:
            for r in self.rolesByProfile[p]:
                roles[r] = 1
        rolesForVirtualServer = roles.keys()
        
        serverRole = commonTools.extractRole(serverId)
        if not serverRole in rolesForVirtualServer:
            clientId = virtualServer.virtualServerIds[clientToken]
            clientRole = commonTools.extractRole(clientId)
            if clientRole == 'ncards' and serverRole != 'ncards':
                # hack so that we have a serverInfos entry for the role
                virtualServer.serverInfos[serverNameAndPort + serverRole] = {
                        'accessor': virtualServer.serverInfos[
                                        serverNameAndPort]['accessor'],
                        'role': serverRole
                    }
            else:
                print serverRole, 'not in roles defined by profiles for', serverDispatcherId
                raise faults.RoleNotInProfiles(serverRole)

        virtualServer.serverAccessors[serverId] = virtualServer.serverInfos[
                serverNameAndPort]['accessor']
        print 'Registered virtual server %s (for %s:%s)' % (
                serverId, serverHostName, serverPort)
        virtualServer.markCoreAsDirty()

    def unregisterServer(self, serverHostName, serverPort):
        virtualServer = self.getVirtualServer(None)
        clientToken = context.getVar('clientToken')
        if not virtualServer.virtualServerIds.has_key(clientToken):
            raise faults.UnknownApplicationToken(clientToken)
        serverNameAndPort = '%s:%s' % (serverHostName, serverPort)
        if not virtualServer.serverInfos.has_key(serverNameAndPort):
            raise faults.UnregisteredServer(serverHostName, serverPort)
        serverInfo = virtualServer.serverInfos[serverNameAndPort]
        serverAccessor = serverInfo['accessor']

        # Unregister every virtual servers running on this server.
        for virtualServerId, virtualServerAccessor in \
                virtualServer.serverAccessors.items():
            if virtualServerAccessor == serverAccessor:
                del virtualServer.serverAccessors[virtualServerId]

        del virtualServer.defaultAccessors[serverAccessor['role']]
        del virtualServer.serverInfos[serverNameAndPort]
        virtualServer.markCoreAsDirty()

    def unregisterVirtualServer(self, serverId):
        virtualServer = self.getVirtualServer(None)
        clientToken = context.getVar('clientToken')
        if not virtualServer.virtualServerIds.has_key(clientToken):
            raise faults.UnknownApplicationToken(clientToken)

        if not virtualServer.serverAccessors.has_key(serverId):
            raise faults.UnknownServerId(serverId)
        del virtualServer.serverAccessors[serverId]
        virtualServer.markCoreAsDirty()

    def updateDispatcherIdProfiles(self, dispatcherId, profiles):
        print 'updateDispatcherIdProfiles:', dispatcherId, profiles
        virtualServer = self.getVirtualServer(None)
        clientToken = context.getVar('clientToken')
        if not virtualServer.virtualServerIds.has_key(clientToken):
            raise faults.UnknownApplicationToken(clientToken)

        currentRoles = ['']
        for p in virtualServer.virtualServerIdProfiles[dispatcherId]:
            currentRoles += self.rolesByProfile[p]
        currentRolesUniq = {}
        for r in currentRoles:
            currentRolesUniq[r] = None

        virtualServer.virtualServerIdProfiles[dispatcherId] = profiles

        self.initProfiles() # so we have up-to-date profiles information
        futureRoles = ['']
        for p in profiles:
            try:
                futureRoles += self.rolesByProfile[p]
            except KeyError:
                # a profile we didn't know about; ignore silently
                pass
                
        futureRolesUniq = {}
        for r in futureRoles:
            futureRolesUniq[r] = None

        rolesToAdd = []
        for r in futureRolesUniq.keys():
            if not r in currentRoles:
                rolesToAdd.append(r)


        rolesToRemove = []
        for r in currentRolesUniq.keys():
            if not r in futureRoles:
                rolesToRemove.append(r)

        for serverInfo in virtualServer.serverInfos.values():
            serverAccessor = serverInfo['accessor']
            serverId = commonTools.makeApplicationId(
                    'glasnost://%s' % serverAccessor['serverHostName'],
                    serverInfo['role'])

            if serverInfo['role'] in rolesToAdd:
                print 'Calling addDispatcher(%s) for %s' % (
                        dispatcherId, serverId)
                context.push(_level = 'registerDispatcherId',
                             directServerAccessor = serverAccessor)
                try:
                    callServer(serverId, 'addDispatcher', [
                            serverId, getApplicationToken(),
                            context.getVar('userToken'), dispatcherId])
                finally:
                    context.pull(_level = 'registerDispatcherId')

            if serverInfo['role'] in rolesToRemove:
                print 'FIXME: should removeDispatcher'


dispatcher = Dispatcher()


if __name__ == "__main__":
    dispatcher.launch(applicationName, applicationRole)

