#!/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 Elections Server"""

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


import sys

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

import glasnost

import glasnost.common.context as context
from glasnost.common.ElectionsCommon import *
import glasnost.common.faults as faults
import glasnost.common.tools_new as commonTools
import glasnost.common.xhtmlgenerator as X

from glasnost.server.ObjectsServer import register, ObjectServerMixin, \
        AdminServerMixin, ObjectsServer
from glasnost.server.tools import *

from glasnost.proxy.AtomsProxy import Atom
from glasnost.proxy.CacheProxy import invalidateValue
from glasnost.proxy.DispatcherProxy import getApplicationId
from glasnost.proxy.GroupsProxy import getSetContainedIds


applicationName = 'ElectionsServer'
applicationRole = 'elections'
dispatcher = None


class AdminElections(AdminServerMixin, AdminElectionsCommon):
    pass
register(AdminElections)


class ElectionMixin(ObjectServerMixin):
    def checkAddIsPossible(self):
        ObjectServerMixin.checkAddIsPossible(self)
        if self.winnersGroupId:
            group = getProxyForServerRole('groups').getObject(
                    self.winnersGroupId)
            if len(group.getContainedIds()) > 0:
                raise faults.WinnersGroupNotEmpty()

    def importVoteFromXmlRpc(self, voteImport):
        return commonTools.importThing(voteImport)


class AbstractElection(ElectionMixin, AbstractElectionCommon):
    pass
register(AbstractElection)


class ElectionAverage(ElectionMixin, ElectionAverageCommon):
    def compute(self):
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        self.candidateIds = getSetContainedIds(self.candidatesSet)
        voterIds = self.getVoterIds(virtualServerId)

        if not self.canCompute():
            if self.ratings is not None:
                del self.ratings
            if self.winnerIds is not None:
                del self.winnerIds
            return

        # Compute the ratings.
        ratings = {}
        for candidateId in self.candidateIds:
            ratings[candidateId] = 0
        count = 0
        votes = getProxyForServerRole('ballots').getElectionVotes(
                self.voteTokens, voterIds)
        if self.weightingsGradeId is not None:
            weightingsGrade = getProxyForServerRole('grades').getObject(
                    self.weightingsGradeId)
            weightingsById = weightingsGrade.repairMarks(
                weightingsGrade.marks, voterIds)
            weightings = getProxyForServerRole('ballots'
                    ).getElectionWeightings(self.voteTokens, weightingsById)
        else:
            weightings = None
        for vote in votes:
            if not vote.isAbstention() and not vote.isBlank(self.candidateIds):
                if weightings is not None:
                    weighting = weightings[vote.voterToken]
                else:
                    weighting = 1
                count = count + weighting
                marks = vote.repairMarks(vote.marks, self.candidateIds)
                for candidateId, mark in marks.items():
                    ratings[candidateId] += weighting * mark
        if count > 0:
            count = float(count)
            for candidateId in self.candidateIds:
                ratings[candidateId] = ratings[candidateId] / count
        self.ratings = ratings

        # Compute the winners.
        if self.winnersCount > 0:
            winnerIds = self.candidateIds[:]
            def winnersSorter(x, y, ratings = ratings):
                return cmp(ratings[y], ratings[x])
            winnerIds.sort(winnersSorter)
            winnersCount = self.winnersCount
            if len(winnerIds) > 0 and winnersCount < len(winnerIds):
                lastRating = ratings[winnerIds[winnersCount - 1]]
                for winnerId in winnerIds[winnersCount:]:
                    if ratings[winnerId] < lastRating:
                        break
                    winnersCount = winnersCount + 1
            self.winnerIds = winnerIds[:winnersCount]
        elif self.winnerIds is not None:
            del self.winnerIds

        # Update the winners' group, if it exists.
        if self.winnerIds and self.winnersGroupId is not None:
            group = getProxyForServerRole('groups').getObject(
                    self.winnersGroupId)
            group.membersSet = self.winnerIds
            group.nonMembersSet = []
            getProxyForServerRole('groups').modifyObject(group)

        # Update the winners' grade, if it exists.
        if self.winnersGradeId is not None:
            grade = getProxyForServerRole('grades').getObject(
                    self.winnersGradeId)
            grade.membersSet = self.candidateIds
            grade.marks = self.ratings
            getProxyForServerRole('grades').modifyObject(grade)
register(ElectionAverage)


def _addToPotentialWinners(candidateId, newWinnerIds, winnerIds, ratings):
    if candidateId in newWinnerIds:
        return
    if candidateId in winnerIds:
        return
    newWinnerIds.append(candidateId)
    rating = ratings[candidateId]
    for tieId in rating['ties']:
        _addToPotentialWinners(tieId, newWinnerIds, winnerIds, ratings)
    for lossId in rating['losses']:
        _addToPotentialWinners(lossId, newWinnerIds, winnerIds, ratings)


class ElectionCondorcet(ElectionMixin, ElectionCondorcetCommon):
    def compute(self):
        virtualServerId = self.getServer().computeVirtualServerId(self.id)
        self.candidateIds = getSetContainedIds(self.candidatesSet)
        voterIds = self.getVoterIds(virtualServerId)

        if not self.canCompute():
            if self.pairwiseMatrix is not None:
                del self.pairwiseMatrix
            if self.ratings is not None:
                del self.ratings
            if self.winnerIds is not None:
                del self.winnerIds
            return

        # Compute the pairwise matrix.
        pairwiseMatrix = {}
        for candidateId in self.candidateIds:
            pairwiseMatrix[candidateId] = row = {}
            for candidateId2 in self.candidateIds:
                row[candidateId2] = 0
        votes = getProxyForServerRole('ballots').getElectionVotes(
                self.voteTokens, voterIds)
        if self.weightingsGradeId is not None:
            weightingsGrade = getProxyForServerRole('grades').getObject(
                    self.weightingsGradeId)
            weightingsById = weightingsGrade.repairMarks(
                    weightingsGrade.marks, voterIds)
            weightings = getProxyForServerRole('ballots'
                    ).getElectionWeightings(self.voteTokens, weightingsById)
        else:
            weightings = None
        for vote in votes:
            if not vote.isAbstention() and not vote.isBlank(self.candidateIds):
                if weightings is not None:
                    weighting = weightings[vote.voterToken]
                else:
                    weighting = 1
                marks = vote.repairMarks(vote.marks, self.candidateIds)
                for candidateId, mark in marks.items():
                    for candidateId2, mark2 in marks.items():
                        if candidateId == candidateId2:
                            continue
                        if mark == mark2:
                            pairwiseMatrix[candidateId][candidateId2] += \
                                weighting / 2.0
                        elif mark < mark2:
                            pairwiseMatrix[candidateId][candidateId2] += \
                                weighting
        self.pairwiseMatrix = pairwiseMatrix

        # Compute the ratings.
        ratings = {}
        for candidateId in self.candidateIds:
            rating = {
                'wins': [],
                'losses': [],
                'ties': [],
                'worstDefeat': 0,
                }
            for candidateId2 in self.candidateIds:
                if candidateId == candidateId2:
                    continue
                delta = pairwiseMatrix[candidateId][candidateId2] \
                        - pairwiseMatrix[candidateId2][candidateId]
                if delta > 0:
                    rating['wins'].append(candidateId2)
                elif delta < 0:
                    rating['losses'].append(candidateId2)
                    if delta < rating['worstDefeat']:
                        rating['worstDefeat'] = -delta
                else:
                    rating['ties'].append(candidateId2)
            ratings[candidateId] = rating
        self.ratings = ratings

        # Compute the winners.
        if self.winnersCount >= len(self.candidateIds):
            self.winnerIds = self.candidateIds[:]
        elif self.winnersCount > 0:
            winnerIds = []
            remaining = self.candidateIds[:]
            for k in range(self.winnersCount):
                if len(winnerIds) >= self.winnersCount:
                    break
                # If there is a candidate with no loss and no tie, then it is a
                # winner.
                for remainingId in remaining:
                    remainingRating = ratings[remainingId]
                    isWinner = 1
                    for lossId in remainingRating['losses']:
                        if lossId not in winnerIds:
                            isWinner = 0
                            break
                    if not isWinner:
                        continue
                    for tieId in remainingRating['ties']:
                        if tieId not in winnerIds:
                            isWinner = 0
                            break
                    if not isWinner:
                        continue
                    winnerIds.append(remainingId)
                    remaining.remove(remainingId)
                    break
                else:
                    # If there is a group of candidates who lose only between
                    # them and if this group is smaller or equal to the
                    # remaining winners to find, add this group to the winners.
                    smallestGroup = remaining[:]
                    for remainingId in remaining:
                        newWinnerIds = []
                        _addToPotentialWinners(remainingId, newWinnerIds,
                                               winnerIds, ratings)
                        if len(newWinnerIds) < len(smallestGroup):
                            smallestGroup = newWinnerIds[:]
                        if len(winnerIds) + len(newWinnerIds) \
                           <= self.winnersCount:
                            for winnerId in newWinnerIds:
                                winnerIds.append(winnerId)
                                remaining.remove(winnerId)
                            break
                    else:
                        # No candidate respects the Condorcet criterion.
                        # Choose the ones with the narrowest worst defeat.
                        narrowestWorstDefeat = None
                        newWinnerIds = None
                        for remainingId in smallestGroup:
                            remainingRating = ratings[remainingId]
                            if narrowestWorstDefeat is None \
                               or remainingRating['worstDefeat'] \
                               < narrowestWorstDefeat:
                                narrowestWorstDefeat = remainingRating[
                                    'worstDefeat']
                                newWinnerIds = [remainingId]
                            elif remainingRating['worstDefeat'] \
                                 == narrowestWorstDefeat:
                                newWinnerIds.append(remainingId)
##                         if len(winnerIds) + len(newWinnerIds) \
##                            <= self.winnersCount:
                        for winnerId in newWinnerIds:
                            winnerIds.append(winnerId)
                            remaining.remove(winnerId)
            self.winnerIds = winnerIds
        elif self.winnerIds is not None:
            del self.winnerIds

        # Update the winners' group, if it exists.
        if self.winnerIds and self.winnersGroupId is not None:
            group = getProxyForServerRole('groups').getObject(
                    self.winnersGroupId)
            group.membersSet = self.winnerIds
            group.nonMembersSet = []
            getProxyForServerRole('groups').modifyObject(group)
register(ElectionCondorcet)


class ElectionsServer(ElectionsCommonMixin, ObjectsServer):
    def abstainForVote(self, objectId, previousVoteToken):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        if object.state != 'running':
            raise faults.UserAccessDenied()
        if object.voteTokens is None:
            object.voteTokens = []
        if previousVoteToken \
           and object.voteTokens is not None \
           and previousVoteToken in object.voteTokens:
            del object.voteTokens[object.voteTokens.index(
                previousVoteToken)]
            if len(object.voteTokens) == 0:
                del object.voteTokens
        virtualServer.markObjectAsDirty(object)

    def addObjectXmlRpc(self, objectImport):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = commonTools.importThing(objectImport)
        # User must be an admin or (a writer and a writer of this object))
        if not self.isAdmin() and not (
                self.useAdminWritersSet
                and getProxyForServerRole('identities').setContainsUser(
                self.getAdminCore(check = 0).writersSet) and (
                getProxyForServerRole('identities').setContainsUser(
                object.writersSet))) \
                or object.state != 'draft':
            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

    def canDeleteObject(self, objectId):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return 0
        object = virtualServer.loadObjectCore(objectId)
        isAdmin = self.isAdmin()
        if not isAdmin and not getProxyForServerRole('identities'
                ).setContainsUser(object.writersSet):
            return 0
        if not isAdmin and object.state != 'draft':
            return 0
        return 1

    def canModifyObject(self, objectId):
        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
        isAdmin = self.isAdmin()
        if not isAdmin and not getProxyForServerRole('identities'
                ).setContainsUser(object.writersSet):
            return 0
        if not isAdmin and object.state != 'draft':
            return 0
        return 1

    def canVote(self, objectId):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return 0
        object = virtualServer.loadObjectCore(objectId)
        if object.state != 'running':
            return 0
        return 1

    def computeObject(self, objectId):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        virtualServer.objects[objectId].compute()
        invalidateValue(objectId)

    def getBallotKind(self, objectId):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        return object.ballotKind

    def getLastObjectIds(
            self, objectsCount, possibleAuthorsSet, possibleReadersSet,
            possibleWritersSet, possibleStates):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        isAdmin = self.isAdmin()
        try:
            possibleAuthorIds = getSetContainedIds(
                    possibleAuthorsSet, ['people'], raiseWhenUncountable = 1)
        except faults.UncountableGroup:
            possibleAuthorIds = 'everybody'
        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()
        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 possibleStates and not object.state in possibleStates:
                continue
            if not isAdmin and not getProxyForServerRole(
                    'identities').setContainsUser(object.readersSet):
                continue
            if not self.getLastObjectIds_filter(
                    possibleAuthorIds, 1, object.authorsSet):
                continue
            if not self.getLastObjectIds_filter(
                    possibleReaderIds, 1, object.readersSet):
                continue
            if not self.getLastObjectIds_filter(
                    possibleWriterIds, 1, object.writersSet):
                continue
            result.append(objectId)
            if objectsCount != -1 and len(result) >= objectsCount:
                break
        return result

    def getLastObjectIdsWithVoter(self, objectsCount, voterId, possibleStates):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        objectIds = virtualServer.objects.keys()
        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 not self.isAdmin() \
                    and not getProxyForServerRole('identities'
                            ).setContainsUser(object.readersSet):
                continue
            if possibleStates and not object.state in possibleStates:
                continue
            if not voterId in object.getVoterIds(virtualServerId):
                continue
            result.append(objectId)
            if len(result) >= objectsCount:
                break
        return result

    def getVoteXmlRpc(self, objectId, voteToken):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        if object.votes is None or not object.votes.has_key(voteToken):
            raise faults.UnknownVoteToken(voteToken)
        vote = object.votes[voteToken]
        result = vote.exportToXmlRpc()
        return result

    def modifyObjectXmlRpc(self,objectImport):
        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()
            previousState = object.state
            newState = objectChanges.state
            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()
            if not self.isAdmin() and previousState != newState \
                    and (previousState != 'draft'
                         or newState != 'proposed'):
                raise faults.UserAccessDenied()
            object.checkModifyIsPossible(objectChanges)
            object.modify(objectChanges)
            object.compute()
            object.saveNonCore()
        finally:
            object.releaseNonCore()
        virtualServer.markObjectAsDirty(object)
        invalidateValue(object.id)
        return object.version

    def pesterAbstentionnistsXmlRpc(self, objectId, httpHostName, httpPort,
                                    httpScriptDirectoryPath):
        httpHostName = iso8859_15(httpHostName)
        httpPort = iso8859_15(httpPort)
        httpScriptDirectoryPath = iso8859_15(httpScriptDirectoryPath)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        if not virtualServer.canLoadObjectCore(objectId):
            return 0
        object = virtualServer.loadObjectCore(objectId)
        if object.state != 'running':
            return 0


        if httpHostName:
            context.setVar('httpHostName', httpHostName)
        if httpPort:
            context.setVar('httpPort', httpPort)
        if httpScriptDirectoryPath:
            context.setVar(
                'httpScriptDirectoryPath', httpScriptDirectoryPath)
        electionUrl = X.idUrl(objectId).getAsAbsoluteUrl()

        fromAddress = virtualServer.adminEmailAddress
        abstentionnistsCount = 0
        for voterId in object.getVoterIds(virtualServerId):
            voter = getProxyForServerRole('identities').getObject(voterId)
            if voter.voteTokens is not None \
               and voter.voteTokens.has_key(objectId):
                continue
            abstentionnistsCount += 1
            toAddress = voter.email
            message = """\
REMINDER

The election "%(label)s" is running.
You can vote at the URL:
%(url)s

Please, do it now!
""" % {
                    'label': object.getLabel(),
                    'url': electionUrl,
                    }
            sendMail(
                    mailFrom = fromAddress,
                    mailTo = toAddress,
                    mailSubject = "[Glasnost] Election in Progress",
                    mailMessage = message,
                    mailPerson = voter,
                    )
        return abstentionnistsCount

    def registerPublicMethods(self):
        ObjectsServer.registerPublicMethods(self)
        self.registerPublicMethod('abstainForVote')
        self.registerPublicMethod('canVote')
        self.registerPublicMethod('computeObject')
        self.registerPublicMethod('getBallotKind')
        self.registerPublicMethod('getLastObjectIdsWithVoter')
        self.registerPublicMethod('getVote', self.getVoteXmlRpc)
        self.registerPublicMethod('pesterAbstentionnists',
                                         self.pesterAbstentionnistsXmlRpc)
        self.registerPublicMethod('vote')

    def repairVirtualServer(self, virtualServer, version):
        changed = 0
        if version < 2006:
            for object in virtualServer.objects.values():
                if hasattr(object, 'isSecret'):
                    changed = 1
                    object.ballotKind = object.isSecret
                    del object.isSecret
        if version < 3005:
            for object in virtualServer.objects.values():
                if object.readersSet is None and object.writersSet is None:
                    changed = 1
                    object.writersSet = object.authorsSet
                    if object.writersSet is not None:
                        object.writersSet = object.writersSet[:]
                    if object.state in ['refused', 'trashed']:
                        del object.state # => object.state = 'draft'
                    if object.state == 'draft':
                        object.readersSet = object.authorsSet
                        if object.readersSet is not None:
                            object.readersSet = object.readersSet[:]
        if version < 4000:
            changed = virtualServer.admin.repair(4000) or changed
            for id, object in virtualServer.objects.items():
                newId = repairId(id)
                if newId:
                    changed = 1
                    del virtualServer.objects[id]
                    virtualServer.objects[newId] = object
                changed = object.repair(4000) or changed
                if not object.__dict__.has_key('language'):
                    changed = 1
                    object.language = 'fr'
        if version < 5004:
            changed = virtualServer.admin.repair(5004) or changed
            for id, object in virtualServer.objects.items():
                changed = object.repair(5004) or changed
        if version <= 1013000:
            admin = virtualServer.admin
            if admin.id is None:
                changed = 1
                admin.id = '%s/__admin__' % virtualServer.virtualServerId
        if version <= 1018000:
            for object in virtualServer.objects.values():
                if object.voteTokens is None:
                    continue
                for i in range(len(object.voteTokens)):
                    voteToken = object.voteTokens[i]
                    if not voteToken.startswith('glasnost://'):
                        changed = 1
                        object.voteTokens[i] = '%s/votes/%s' % (
                            commonTools.extractDispatcherId(
                            virtualServer.virtualServerId),
                            voteToken)
        if changed:
            virtualServer.markAllAsDirtyFIXME()

    def vote(self, objectId, voteToken, previousVoteToken):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        if object.state != 'running':
            raise faults.UserAccessDenied()
        if object.voteTokens is None:
            object.voteTokens = []
        if previousVoteToken \
           and previousVoteToken in object.voteTokens:
            del object.voteTokens[object.voteTokens.index(
                previousVoteToken)]
        object.voteTokens.append(voteToken)
        virtualServer.markObjectAsDirty(object)


electionsServer = ElectionsServer()


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