configurationengine/source/cone/public/container.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
--- /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)