This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
glasnost/shared/server/ObjectsServer.py

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)