3086 lines
111 KiB
Python
3086 lines
111 KiB
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 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, sort):
|
|
virtualServerId = context.getVar('applicationId')
|
|
virtualServer = self.getVirtualServer(virtualServerId)
|
|
result = []
|
|
items = virtualServer.objects.items()
|
|
if sort:
|
|
reverse = 0
|
|
if sort[0] == '-':
|
|
sort = sort[1:]
|
|
reverse = 1
|
|
items.sort(lambda x,y: cmp(getattr(x[1], sort), getattr(y[1], sort)))
|
|
for objectId, objectCore in 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)
|
|
|