--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/configurationengine/source/cone/public/container.py Thu Mar 11 17:04:37 2010 +0200
@@ -0,0 +1,794 @@
+#
+# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description:
+#
+
+"""
+Container classes.
+Mainly internal classed that the public data model uses internally.
+"""
+
+import re
+import pickle
+import logging
+import utils, exceptions
+
+def object_container_filter(obj,**kwargs):
+ """ Create a list of filter functions for each argument """
+ filters=[]
+ if kwargs.has_key('name'):
+ filters.append(lambda x: re.match(kwargs.get('name'), x._name))
+ if kwargs.has_key('path'):
+ filters.append(lambda x: re.match(kwargs.get('path'), x._path()))
+ if kwargs.has_key('type'):
+ filters.append(lambda x: isinstance(x, kwargs.get('type')))
+ if kwargs.has_key('filters'):
+ filters += kwargs.get('filters')
+ ret = []
+ for sobj in utils.get_list(obj):
+ if utils.filter(obj,filters):
+ ret.append(sobj)
+
+ return ret
+
+def _apply_filter(obj,filters):
+ """ Create a list of filter functions for each argument """
+ if utils.filter(obj,filters):
+ return [obj]
+ else:
+ return []
+
+""" object container adding policies """
+REPLACE = 0
+APPEND = 1
+PREPEND = 2
+ERROR = 3
+
+class DataContainer(object):
+ """
+ Class for data containers.
+ Container is a data storage that can hold several keys, where each key is unique. Each key however
+ can hold several values, where the active value is the last one added.
+
+ Example:
+ data = {'key1' :[1,2,3,4],
+ 'key2' :['foo','bar],
+ 'key3' :['testing'],
+ 'path/to/key' :['some','value','in','here','too']}
+
+ The active values for keys are the last ones in the array. E.g. key1 = 4.
+ """
+ def __init__(self):
+ self.data = {}
+
+ def list_keys(self):
+ """
+ List all keys of the DataStorage.
+ """
+ return self.data.keys()
+
+ def add_value(self,key,value):
+ """
+ Add the value as a topmost item for the given key.
+ @param key: name for the key to store the data.
+ @param value: the value to store.
+ """
+ if self.data.has_key(key):
+ self.data[key].append(value)
+ else:
+ self.data[key] = [value]
+ return
+
+ def remove_value(self,key,value):
+ """
+ remove individual value of the key value array
+ """
+ self.data[key].remove(value)
+ return
+
+ def remove_key(self,key):
+ del self.data[key]
+ return
+
+ def get_value(self,key):
+ """
+ self.data = {'key1' :[1,2,3,4],
+ 'key2' :['foo','bar],
+ 'key3' :['testing'],
+ 'path/to/key' :['some','value','in','here','too']}
+ self.get_value('key1')
+ 4
+ """
+ return self.data[key][-1]
+
+ def get_values(self,key):
+ """
+ return a copy of data values inside the container
+ """
+ values = []
+ values.extend(self.data[key])
+ return values
+
+ def flatten(self):
+ """
+ return a new dictionary of the DataContainer data with only single values for each key,
+ instead of the array of values.
+ """
+ rest = {}
+ for key in self.data.keys():
+ rest[key] = self.get_value(key)
+ return rest
+
+ def clear(self):
+ """
+ Remove all data from the container.
+ """
+ return self.data.clear()
+
+class ContainerBase(object):
+
+ def _set_parent(self, newparent):
+ """
+ @param newparent: The new parent object
+ @return: None
+ """
+ self._parent = newparent
+
+ def _get_parent(self):
+ """
+ @return: existing parent object
+ """
+ return self._parent
+
+ def _del_parent(self):
+ """
+ Set the current parent to None
+ """
+ self._parent = None
+
+ parent = property(_get_parent, _set_parent,_del_parent)
+
+
+class ObjectProxy(ContainerBase):
+ """
+ An object proxy class. The ObjectProxy overrides the python builtin methdo __getattr__
+ to redirect any function/member access to the subobject.
+ """
+ def __init__(self,obj=None):
+ """
+ """
+ self._obj = obj
+ self._parent = None
+
+ def __getattr__(self,name):
+ """
+ direct all not found attribute calls to the sub object getattr
+ """
+ return getattr(self._obj,name)
+
+
+# def _set_parent(self, newparent):
+# """
+# @param newparent: The new parent object
+# @return: None
+# """
+# self._parent = newparent
+# if isinstance(self._obj, ContainerBase):
+# self._obj._set_parent(newparent)
+
+class LoadInterface(ContainerBase):
+ def load(self,ref):
+ file = open(ref,"r")
+ self._parent = None
+ return pickle.load(file)
+
+ def unload(self,ref, obj):
+ """
+ unload or release
+ """
+ file = open(ref,"w")
+ pickle.dump(obj,file)
+ file.close()
+
+ def get_path(self):
+ """
+ Return the path of the configuration resource
+ """
+ return ""
+
+class LoadProxy(ContainerBase):
+ """
+ This class is meant for loading & unloading an object, when it need.
+ """
+ def __init__(self, path, store_interface=None):
+ """
+ @param path: the path which is used in loadin
+ @param store_interface: the loading interface object, which is used.
+ Expects load(path) and dump(obj) functions
+ """
+ self.set('_obj', None)
+ self.set('_parent', None)
+ self.set('path', path)
+ self.set('_storeint', store_interface)
+
+ def __getattr__(self,name):
+ """
+ direct all not found attribute calls to the sub object getattr
+ """
+ if not self._obj:
+ self._load()
+ return getattr(self._obj,name)
+
+ def __setattr__(self, name, value):
+ """
+ direct attribute setting calls to the sub object setattr
+ """
+ if not self._obj:
+ self._load()
+ setattr(self._obj,name,value)
+
+ def __delattr__(self, name):
+ """
+ direct attribute setting calls to the sub object setattr
+ """
+ if not self._obj:
+ self._load()
+ delattr(self._obj,name)
+
+ def _set_parent(self, newparent):
+ """
+ @param newparent: The new parent object
+ @return: None
+ """
+ self.set('_parent',newparent)
+ if self._obj:
+ self._obj._parent = self._parent
+
+ def _set_obj(self, obj):
+ self.set('_obj',obj)
+ # set the same _parent for the actual object as is stored for the proxy
+ self._obj._parent = self._parent
+ self._obj.set_path(self.path)
+
+ def _get_obj(self):
+ if not self._obj:
+ self._load()
+ return self._obj
+
+ def _load(self):
+ # Should the loading of layer external resources be supported?
+ # E.g. resources with absolute path relative to the storage (starts with slash)
+ """ If the loading of the object fails => Raise an InvalidObject exception """
+ try:
+ obj = self._store_interface().load(self.fullpath)
+ self._set_obj(obj)
+ obj.set_ref(utils.resourceref.to_objref(self.path))
+ except exceptions.NotResource,e:
+ logging.getLogger('cone').warning("Loading %s from parent %s failed! %s" % (self.path,self.get_parent_path(), e))
+ raise exceptions.InvalidObject("Invalid configuration object %s" % self.path)
+
+ def _unload(self):
+ if self._obj:
+ self._store_interface().unload(self.fullpath, self._obj)
+ self.set('_obj',None)
+
+ def _store_interface(self):
+ if not self._storeint:
+ self.set('_storeint',self._parent.get_project())
+ return self._storeint
+
+ def set(self,name,value):
+ """
+ Proxy has a specific attribute setting function, because by default all attributes are
+ stored to the actual proxy object
+ """
+ self.__dict__[name] = value
+
+ def get(self,name):
+ """
+ Proxy has also a specific attribute getting function, because by default all attributes are
+ stored to the actual proxy object
+ """
+ return self.__dict__[name]
+
+ def save(self):
+ if hasattr(self._obj,'save'):
+ self._obj.save()
+ self._unload()
+
+ def close(self):
+ if hasattr(self._obj,'close'):
+ self._obj.close()
+
+ def get_parent_path(self):
+ """
+ Return the path of the configuration resource
+ """
+ if self._parent:
+ return utils.resourceref.get_path(self._parent.get_path())
+ else:
+ return ""
+
+ def get_path(self):
+ """
+ Return the path of the configuration resource
+ """
+ if self._obj:
+ return self._obj.get_path()
+ else:
+ return self.path
+
+ @property
+ def fullpath(self):
+ """
+ Return the path of the configuration resource
+ """
+ try:
+ return self._obj.get_full_path()
+ except AttributeError:
+ parent_path = self.get_parent_path()
+ return utils.resourceref.join_refs([parent_path,self.path])
+
+class ObjectContainer(ContainerBase):
+ """
+ An object container class. The ObjectContainer is actually a Tree data structure. Any ObjectContainer
+ instance can include any number of children, that must be instances of ObjectContainer.
+ """
+ def __init__(self,name="",**kwargs):
+ """
+ """
+ if len(name.split(".")) > 1 or len(name.split("/")) > 1:
+ raise exceptions.InvalidRef("Illegal name for ObjectContainer %s" % name)
+ self._name = name
+ self._parent = None
+ self._order = []
+ self._children = {}
+ for arg in kwargs.keys():
+ setattr(self, arg, kwargs.get(arg))
+
+ def __getattr__(self,name):
+ """
+ direct all not found attribute calls to the sub object getattr
+ """
+ try:
+ return self.__dict__['_children'][name]
+ except KeyError:
+ return getattr(super(ObjectContainer),name)
+
+ def _path(self, toparent=None):
+ """
+ Get the path to this ObjectContainer.
+ @param toparent: the _parent object up to which the path is relative. Default value is None.,
+ which gives the fully qualified path
+ @return: The path to the ObjectContainer from toparent
+ """
+ if self == toparent:
+ return ""
+ elif self._parent and self._parent != toparent:
+ # return the path with list index if the given element is in a list
+ if utils.is_list(self.parent._get(self._name)):
+ return self._parent._path(toparent)+"."+"%s[%s]" % (self._name,self.get_index())
+ else:
+ return self._parent._path(toparent)+"."+self._name
+ else:
+ return self._name
+
+ def _add(self, child, policy=REPLACE):
+ """
+ Add a child object.
+ @param child: The child object to add. The child needs to be an instance of ObjectContainer.
+ @param policy: The policy which is used when an object with same name exists already
+ """
+ # check that the child is a supported type
+ if not self._supported_type(child):
+ raise exceptions.IncorrectClassError("Cannot add instance of %s to %s." % (child.__class__,self.__class__))
+ if policy == REPLACE:
+ self._replace(child)
+ elif policy == ERROR:
+ self._error(child)
+ elif policy == APPEND:
+ self._append(child)
+ elif policy == PREPEND:
+ self._prepend(child)
+
+ def _append(self, child):
+ """
+ Add the given child to the proper key. Create a list entry if necessary
+ """
+ child._set_parent(self)
+ if not self._children.has_key(child._name):
+ # skip all internal objects (that start with _)
+ if not child._name.startswith('?'):
+ self._order.append(child._name)
+ self._children[child._name] = child
+ else:
+ """ Create a list under the child name """
+ self._children[child._name] = utils.add_list(self._children[child._name], child)
+ return
+
+ def _prepend(self, child):
+ """
+ Add the given child to the proper key. Create a list entry if necessary
+ """
+ child._set_parent(self)
+ if not self._children.has_key(child._name):
+ # skip all internal objects (that start with _)
+ if not child._name.startswith('?'):
+ self._order.insert(0,child._name)
+ self._children[child._name] = child
+ else:
+ """ Create a list under the child name """
+ self._children[child._name] = utils.prepend_list(self._children[child._name], child)
+ return
+
+ def _replace(self, child):
+ """
+ If the given child already exists => Replace the child,
+ but maintain the current children of that child
+ """
+ child._set_parent(self)
+ # skip all internal objects (that start with _)
+ if not self._children.has_key(child._name):
+ if not child._name.startswith('?'):
+ self._order.append(child._name)
+ else:
+ """ if the existing child is a instance of ObjectContainer,
+ add all children of the existing contianer to this new object """
+ existingchild = self._children[child._name]
+ if isinstance(existingchild, ObjectContainer):
+ for subchild in existingchild._objects():
+ child._add(subchild)
+
+ self._children[child._name] = child
+ return
+
+ def _error(self, child):
+ """
+ If the given child already exists => raise an exception.
+ @raise exceptions.AlreadyExists:
+ """
+ child._set_parent(self)
+ if not self._children.has_key(child._name):
+ # skip all internal objects (that start with _)
+ if not child._name.startswith('?'):
+ self._order.insert(0,child._name)
+ self._children[child._name] = child
+ else:
+ raise exceptions.AlreadyExists('Child %s already exists' % child._name)
+ return
+
+ def _add_to_path(self, path, child, policy=REPLACE):
+ """
+ Add a child object.
+ @param path: the path for the object
+ @param child: The child object to add
+ @param namespace: The namespace of the object, which defines where the object is created
+ """
+ # check that the child is a supported type
+ if not self._supported_type(child):
+ raise exceptions.IncorrectClassError("Cannot add instance of %s to %s Container" % (child.__class__,self.__class__))
+ # ensure that the elements to the namespace exist
+ curelem = self
+ for ppath in utils.dottedref.split_ref(path):
+
+ if not curelem._children.has_key(ppath):
+ # Create missing elem
+ curelem._add(self._default_object(ppath))
+ curelem = curelem._get(ppath)
+ curelem._add(child,policy)
+
+ def _get(self, path):
+ """
+ Get a child object by it path.
+ @return: The child object if it is found.
+ @raise NotFound: when object is not found from the children.
+ """
+
+ try:
+ # traverse to the actual child element
+ curelem = self
+ for pathelem in utils.dottedref.split_ref(path):
+ if utils.dottedref.get_index(pathelem) == None:
+ curelem = curelem._children[pathelem]
+ else:
+ # If the given pathelem is referring to a list
+ name = utils.dottedref.get_name(pathelem)
+ index = utils.dottedref.get_index(pathelem)
+ curelem = utils.get_list(curelem._children[name])[index]
+ return curelem
+ # Catch the KeyError exception from dict and IndexError from list
+ except (KeyError,IndexError):
+ raise exceptions.NotFound("Child %s not found!" % path)
+
+ def _has(self, path):
+ """
+ Returns True if an element under the path is found.
+ @return: Boolean value.
+ """
+
+ try:
+ # traverse to the actual child element
+ curelem = self
+ for pathelem in utils.dottedref.split_ref(path):
+ curelem = curelem._children[pathelem]
+ return True
+ except KeyError:
+ return False
+
+ def _remove(self, path):
+ """
+ Remove a child object by it path.
+ """
+ # if the patherence is a long patherence (dotted name)
+ # first get the _parent object and call the remove to the _parent
+ (parentref,name) = utils.dottedref.psplit_ref(path)
+ if parentref != "":
+ self._get(parentref)._remove(name)
+ elif utils.dottedref.get_index(path) != None and \
+ self._get(utils.dottedref.get_name(path)):
+ # Delete If the given pathelem is referring to a list
+ name = utils.dottedref.get_name(path)
+ index = utils.dottedref.get_index(path)
+ del self._children[name][index]
+ if len(self._children[name]) == 0:
+ del self._order[self._order.index(name)]
+ elif self._get(path) != None: # delete if the child is found
+ del self._children[path]
+ del self._order[self._order.index(path)]
+
+ else:
+ raise exceptions.NotFound("Child %s not found!" % path)
+
+ def _list_traverse(self,**kwargs):
+ """
+ Return a list of all children paths. This function calls internally __traverse__, see it for
+ more details.
+ @return: an unordered list of children paths. The path is relative to this node.
+ """
+ return [child._path(self) for child in self._traverse(**kwargs)]
+
+ def _traverse(self, **kwargs):
+ """
+ The traverse goes recursively through the tree of children of this node and returns a result set as list.
+ Arguments can be passed to it to filter out elements of the result set. All arguments are
+ given as dict, so they must be given with name. E.g. _traverse(name='test')
+ @param name: The node name or part of name which is used as a filter. This is a regular expression (uses internally re.match())
+ @param path: The path name or part of name which is used as a filter. This is a regular expression (uses internally re.match())
+ @param filters: A list of predefined filters can be given as lambda functions. E.g. filters=[lambda x: isinstance(x._obj, FooClass)]
+ @return: a list of ObjectContainer objects.
+ """
+ filterlist=[]
+ if kwargs.has_key('ref'):
+ filterlist.append(lambda x: re.match(kwargs.get('ref'), x.ref))
+ if kwargs.has_key('name'):
+ filterlist.append(lambda x: re.match(kwargs.get('name'), x._name))
+ if kwargs.has_key('path'):
+ filterlist.append(lambda x: re.match(kwargs.get('path'), x._path()))
+ if kwargs.has_key('type'):
+ filterlist.append(lambda x: isinstance(x, kwargs.get('type')))
+ if kwargs.has_key('filters'):
+ filterlist += kwargs.get('filters')
+
+ ret = []
+ for child in self._objects():
+ subchildren = child._tail_recurse(_apply_filter,filters=filterlist)
+ ret += subchildren
+ return ret
+
+ def _find_leaves(self, **kwargs):
+ """
+ Find all leaf nodes in the tree that satisfy the given filtering criteria.
+
+ For possible keyword arguments see _traverse().
+
+ @return: A list of ObjectContainer objects.
+ """
+ # Find all children
+ nodes = self._traverse(**kwargs)
+
+ # Filter out non-leaves
+ return filter(lambda node: len(node._objects()) == 0, nodes)
+
+ def _tail_recurse(self, function, **kwargs):
+ """
+ Run a tail recursion on all container children and execute the given function.
+ 1. function will receive self as argument to it.
+ 2. function will receive all kwargs as argument to it.
+ 3. tail recursion means that the function is executed first and then the
+ recursion continues.
+ @param function: the function which is executed
+ @param kwargs: a list of arguments as dict
+ @return: an list of objects, which can be anything that the funtion returns
+ """
+
+ ret = []
+ ret += function(self,**kwargs)
+ for child in self._objects():
+ try:
+ # We wont add the object to the ret until we know that it is a valid object
+ subchildren = child._tail_recurse(function,**kwargs)
+ #ret += function(child,**kwargs)
+ ret += subchildren
+ except exceptions.InvalidObject,e:
+ # remove the invalid object from this container
+ logging.getLogger('cone').warning('Removing invalid child because of exception %s' % e)
+ self._remove(child._name)
+ continue
+ return ret
+
+ def _head_recurse(self, function,**kwargs):
+ """
+ Run a tail recursion on all container children and execute the given function.
+ 1. function will receive self as argument to it.
+ 2. function will receive all kwargs as argument to it.
+ 3. head recursion means that the recursion continues to the leaf nodes and then the
+ execution of the function begins.
+ @param function: the function which is executed
+ @param kwargs: a list of arguments as dict
+ @return: an list of objects, which can be anything that the funtion returns
+ """
+ ret = []
+ for child in self._objects():
+ try:
+ ret += child._head_recurse(function,**kwargs)
+ ret += function(child,**kwargs)
+ except exceptions.InvalidObject,e:
+ # remove the invalid object from this container
+ logging.getLogger('cone').warning('Removing invalid child because of exception %s' % e)
+ self._remove(child._name)
+ continue
+ return ret
+
+ def _list(self):
+ """
+ Return a array of immediate children names.
+ @return: an unordered list of immediate children path-references
+ """
+ # skip all internal objects (that start with _)
+ return [name for name in self._order if not name.startswith('?')]
+
+ def _objects(self, **kwargs):
+ """
+ Return a array of immediate children.
+ @return: an unordered list of immediate children
+ """
+ ret = []
+ for cname in self._order:
+ try:
+ if object_container_filter(self._children[cname], **kwargs):
+ ret += utils.get_list(self._children[cname])
+ except exceptions.InvalidObject,e:
+ # remove the invalid object from this container
+ logging.getLogger('cone').warning('Removing invalid child because of exception %s' % e)
+ self._remove(cname)
+ continue
+ return ret
+
+ def _get_index(self, name):
+ """
+ Get the index of a child object by its name. The index matches the index
+ of the child object in the _children array.
+ @return: integer.
+ @raise NotFound: when object is not found from the children.
+ """
+
+ try:
+ return self._order.index(name)
+ except KeyError:
+ raise exceptions.NotFound("Child %s not found!" % name)
+
+ def _supported_type(self,obj):
+ """
+ An internal function to check that the given object is a supported for this Tree.
+ This is used in every __add__ operation to check whether the object can be added to the tree.
+ This function should be overloaded by a subclass if the supported types need to be changed.
+ @return: True if object is supported, otherwise false.
+ """
+ return isinstance(obj, ObjectContainer)
+
+ def _default_object(self,name):
+ """
+ An internal function to create a default object for this container in case of __add_to_path__, which
+ creates the intermediate objects automatically.
+ This function should be overloaded by a subclass if the default object need to be changed.
+ @return: A new object.
+ """
+ return ObjectContainer(name)
+
+ def _find_parent(self, **kwargs):
+ """
+ find a _parent object by arguments. You can define any number of object attributes that
+ have to match to the object.
+ Example1:
+ _find_parent(foobar=True) searches for a _parent
+ object which has a member attribute foobar and its value is True.
+ Example2:
+ _find_parent(name="test") searches for a _parent
+ object which has a member attribute name and its value is "test".
+ Example3: type is a special case
+ _find_parent(type=Configuration) searches for a _parent
+ object which is an instance of Configuration (checked with isinstance).
+ @param kwargs:
+ @return: The object that matches the arguments
+ @raise exceptions.NotFound: When no matching parent is found
+ """
+ type = kwargs.get('type', None)
+ if hasattr(self,'_parent') and self._parent != None:
+ found = True
+ for key in kwargs.keys():
+ try:
+ # handle type as a special case
+ if key == 'type':
+ if not isinstance(self._parent, kwargs.get(key)):
+ found = False
+ break
+ elif key == 'match':
+ if not self._parent == kwargs.get(key):
+ found = False
+ break
+ elif not getattr(self._parent, key) == kwargs.get(key):
+ found = False
+ break
+ except AttributeError:
+ found = False
+ break
+ if found:
+ return self._parent
+ else:
+ return self._parent._find_parent(**kwargs)
+ else:
+ raise exceptions.NotFound("Parent not found!")
+
+ def _find_parent_or_default(self, default=None,**kwargs):
+ """
+ Calls internally the find parent function, which is encapsulated with try except
+ returns the given default value if find parent raises NotFound exception.
+ """
+ try:
+ return self._find_parent(**kwargs)
+ except exceptions.NotFound:
+ return default
+
+ def set_ref(self,ref):
+ """
+ @param ref: The new reference of the object
+ """
+ self._name = ref
+ self.ref = ref
+
+ def get_ref(self):
+ """
+ @return: The reference of the object.
+ """
+ return self.ref
+
+class ObjectProxyContainer(ObjectProxy,ObjectContainer):
+ """
+ Combines the Container and Proxy classes to one.
+ """
+ def __init__(self,obj=None,name=""):
+ """
+ """
+ ObjectContainer.__init__(self,name)
+ ObjectProxy.__init__(self,obj)
+
+ def __getattr__(self,name):
+ """
+ First check if the requested attr is a children then
+ direct all not found attribute calls to the sub object getattr
+ """
+ try:
+ return self.__dict__['_children'][name]
+ except KeyError:
+ return getattr(self._obj,name)