configurationengine/source/cone/confml/persistentconfml.py
changeset 3 e7e0ae78773e
parent 0 2e8eeb919028
child 5 d2c80f5cab53
--- a/configurationengine/source/cone/confml/persistentconfml.py	Fri Mar 12 08:30:17 2010 +0200
+++ b/configurationengine/source/cone/confml/persistentconfml.py	Tue Aug 10 14:29:28 2010 +0300
@@ -13,9 +13,9 @@
 #
 # Description: 
 #
+import os
 import re
 import logging
-import xml.parsers.expat
 try:
     from cElementTree import ElementTree
 except ImportError:
@@ -27,8 +27,8 @@
         except ImportError:
             from xml.etree import ElementTree
 
-""" cone specific imports """
-from cone.public import persistence, exceptions, api, utils, container
+# cone specific imports
+from cone.public import persistence, exceptions, api, utils, container, parsecontext
 from cone.confml import model
 
 CONFIGURATION_NAMESPACES = ["http://www.s60.com/xml/confml/2","http://www.s60.com/xml/confml/1"]
@@ -42,12 +42,29 @@
     etree = ConfmlWriter().dumps(obj)
     if indent:
         persistence.indent(etree)
-    return ElementTree.tostring(etree)
+    result = ElementTree.tostring(etree)
+    
+    # To make the output the same in linux and windows
+    # (needed to make testing easier)
+    if os.linesep != '\r\n':
+        result = result.replace(os.linesep, '\r\n')
+    
+    return result
 
 def loads(xml):
     return ConfmlReader().loads(xml)
 
+def add_parse_warning(msg, line, type='model.confml'):
+    parsecontext.get_confml_context().handle_problem(
+        api.Problem(msg,
+                    severity = api.Problem.SEVERITY_WARNING,
+                    type     = type,
+                    line     = line))
 
+def add_unknown_element_warning(elem):
+    add_parse_warning("Unknown element '%s'" % elem.tag,
+                      utils.etree.get_lineno(elem),
+                      type='model.confml.unknown_element')
 
 class ConfmlWriter(persistence.ConeWriter):
     """
@@ -69,7 +86,11 @@
         @param xml: The xml which to read. reads only the first object. 
         """
         reader = get_reader_for_elem("configuration")
-        etree = utils.etree.fromstring(xmlstr)
+        try:
+            etree = utils.etree.fromstring(xmlstr)
+        except exceptions.XmlParseError, e:
+            e.problem_type = 'xml.confml'
+            raise e
         return reader.loads(etree)
 
 
@@ -105,7 +126,13 @@
             elem.set("xmlns:xlink",self.xlink_namespace)
         if self.schema_namespace:
             elem.set("xmlns:xs",self.schema_namespace)
-        elem.set("name",obj.get_name()) 
+        if obj.get_name():
+            elem.set("name", obj.get_name()) 
+        if obj.version != None:
+            elem.set("version", obj.version) 
+        if obj.get_id():
+            elem.set('id', obj.get_id())
+
         for child in obj._objects():
             """ Make sure that the object is mapped to an object in this model """
             mobj = child._get_mapper('confml').map_object(child)
@@ -137,8 +164,11 @@
         self.schema_namespaces         = SCHEMA_NAMESPACES
 
     def loads(self, etree):
-        configuration = model.ConfmlConfiguration("")
-        configuration.set_name(etree.get("name") or 'unknown') 
+        configuration = model.ConfmlConfiguration(name=etree.get("name"),
+                                                  id=etree.get("id"),
+                                                  version=etree.get("version"))
+        configuration.lineno = utils.etree.get_lineno(etree)
+        # configuration.set_name(etree.get("name") or 'unknown') 
         configuration.set_ref(etree.get("name") or 'unknown')
         for elem in etree.getchildren():
             # At the moment we ignore the namespace of elements
@@ -149,7 +179,7 @@
                 if obj:
                     configuration.add(obj)
             except exceptions.ConePersistenceError,e:
-                logging.getLogger('cone').warning("Could not parse element %s. Exception: %s" % (elem,e))
+                add_unknown_element_warning(elem)
                 continue
         return configuration
 
@@ -202,6 +232,7 @@
 
     def loads(self,etree):
         metaelem = model.ConfmlMeta()
+        metaelem.lineno = utils.etree.get_lineno(etree)
         for elem in etree.getchildren():            
             (namespace,elemname) = get_elemname(elem.tag)
             attributes = {}
@@ -250,6 +281,7 @@
 
     def loads(self,elem):
         desc = model.ConfmlDescription(elem.text)
+        desc.lineno = utils.etree.get_lineno(elem)
         return desc
 
 class ConfigurationProxyWriter(ConfmlWriter):
@@ -295,8 +327,9 @@
         """
         @param elem: The xml include elem
         """
-        
-        return api.ConfigurationProxy(self.parse_include(elem))
+        proxy = api.ConfigurationProxy(self.parse_include(elem))
+        # proxy.lineno = utils.etree.get_lineno(elem)
+        return proxy
 
     def parse_include(self,include):
         #print "Found include %s" % include.get('href').replace('#/','')
@@ -321,10 +354,17 @@
         @param obj: The Configuration object 
         """
         elem = ElementTree.Element('feature', 
-                                   {'ref' : obj.get_ref(),
-                                    'name' : obj.get_name()})
+                                   {'ref' : obj.get_ref()})
+        if obj.get_name():
+            elem.set('name', obj.get_name())
         if obj.get_type():
             elem.set('type', obj.get_type())
+        if obj.get_id() != None:
+            elem.set('id', obj.get_id())
+        if obj.get_relevant() != None:
+            elem.set('relevant', obj.get_relevant())
+        if obj.get_constraint() != None:
+            elem.set('constraint', obj.get_constraint())
         for child in obj._objects():
             """ Make sure that the object is mapped to an object in this model """
             mobj = child._get_mapper('confml').map_object(child)
@@ -360,7 +400,14 @@
             feature = model.ConfmlFeature(elem.get('ref'))
         if elem.get('name'):
             feature.set_name(elem.get('name'))
+        if elem.get('id') != None:
+            feature.set_id(elem.get('id'))
+        if elem.get('relevant') != None:
+            feature.set_relevant(elem.get('relevant'))
+        if elem.get('constraint') != None:
+            feature.set_constraint(elem.get('constraint'))
         feature.set_type(type)
+        feature.lineno = utils.etree.get_lineno(elem)
         for elem in elem.getchildren():
             # At the moment we ignore the namespace of elements
             (namespace,elemname) = get_elemname(elem.tag)
@@ -369,7 +416,7 @@
                 obj = reader.loads(elem)
                 feature.add(obj)
             except exceptions.ConePersistenceError,e:
-                logging.getLogger('cone').warning("Could not parse element %s. Exception: %s" % (elem,e))
+                add_unknown_element_warning(elem)
                 continue
         return feature
 
@@ -396,6 +443,9 @@
         if obj.get_value() is not None: objdict['value'] = obj.get_value()
         if obj.map is not None: objdict['map'] = obj.map
         if obj.relevant is not None: objdict['relevant'] = obj.relevant
+        if obj.display_name is not None: objdict['displayName'] = obj.display_name
+        if obj.map_value is not None: objdict['mapValue'] = obj.map_value
+
         elem = ElementTree.Element('option', objdict)
         
         return elem
@@ -422,11 +472,16 @@
         name = elem.get('name')
         value = elem.get('value')
         optmap = elem.get('map')
+
         if value == None and optmap == None:
             logging.getLogger('cone').warning("Encountered option with no value")
             option = None
         else:
-            option = api.Option(name, value, map=optmap, relevant=elem.get('relevant'))
+            option = api.Option(name, value, map=optmap, 
+                                relevant=elem.get('relevant'),
+                                map_value=elem.get('mapValue'), 
+                                display_name=elem.get('displayName'))
+            option.lineno = utils.etree.get_lineno(elem) 
         return option
 
 
@@ -471,6 +526,7 @@
         """
         href = elem.get('{%s}href' % XLINK_NAMESPACES[0])
         obj = model.ConfmlIcon(href)
+        obj.lineno = utils.etree.get_lineno(elem)
         return obj
 
 
@@ -481,7 +537,7 @@
         Class method to determine if this ConfmlWriter supports writing
         of the given class name
         """
-        if classname=="ConfmlProperty":
+        if classname=="Property":
             return True
         else:
             return False
@@ -518,7 +574,8 @@
         """
         @param elem: The xml include elem
         """
-        option = model.ConfmlProperty(name=elem.get('name'),value=elem.get('value'), unit=elem.get('unit'))
+        option = api.Property(name=elem.get('name'),value=elem.get('value'), unit=elem.get('unit'))
+        option.lineno = utils.etree.get_lineno(elem)
         return option
 
 
@@ -574,6 +631,7 @@
         elem_name = utils.xml.split_tag_namespace(elem.tag)[1]
         facet_class = self.MAPPING[elem_name]
         obj = facet_class(elem.get('value'))
+        obj.lineno = utils.etree.get_lineno(elem)
         return obj
 
 
@@ -584,9 +642,8 @@
         Class method to determine if this ConfmlWriter supports writing
         of the given class name
         """
-        if classname=="Data":
-            return True
-        if classname=="DataContainer":
+        if classname=="Data" or \
+           classname=="DataContainer":
             return True
         else:
             return False
@@ -597,14 +654,16 @@
         """
         # Create a data hierarchy of the 
         elem = ElementTree.Element(obj.get_ref())
-        if hasattr(obj,'get_value') and obj.get_value() and not obj.get_map():
+        if hasattr(obj,'get_map') and obj.get_map() is not None:
+            elem.set('map', obj.get_map())
+        elif hasattr(obj,'get_value') and obj.get_value() is not None:
             elem.text = obj.get_value()
-        elif hasattr(obj,'get_map') and obj.get_map():
-            elem.set('map', obj.get_map())
         if hasattr(obj,'template') and obj.template == True:
             elem.set('template','true')
         if hasattr(obj,'policy') and obj.policy != '':
             elem.set('extensionPolicy',obj.policy)
+        if hasattr(obj,'empty') and obj.empty == True:
+            elem.set('empty','true')
         for child in obj._objects():
             writer = DataWriter()
             childelem = writer.dumps(child)
@@ -634,13 +693,14 @@
         
         (namespace,elemname) = get_elemname(elem.tag)
         obj = api.DataContainer(elemname, container=True)
+        obj.lineno = utils.etree.get_lineno(elem)
         for elem in elem.getchildren():
             try:
                 reader = ElemReader(attr='data')
                 childobj = reader.loads(elem)
                 obj.add(childobj)
             except exceptions.ConePersistenceError,e:
-                logging.getLogger('cone').warning("Could not parse element %s. Exception: %s" % (elem,e))
+                add_unknown_element_warning(elem)
                 continue
         return obj
 
@@ -663,8 +723,9 @@
         @param obj: The Configuration object 
         """
         elem = ElementTree.Element('view', 
-                                   {'id' : obj.get_ref(),
-                                    'name' : obj.get_name()})
+                                   {'name' : obj.get_name()})
+        if obj.id != None:  
+            elem.set('id', obj.id)
         for child in obj._objects():
             """ Make sure that the object is mapped to an object in this model """
             mobj = child._get_mapper('confml').map_object(child)
@@ -696,6 +757,7 @@
         vid = elem.get('id')
         name = elem.get('name')
         view = model.ConfmlView(name, id=vid)
+        view.lineno = utils.etree.get_lineno(elem)
         for elem in elem.getchildren():
             # At the moment we ignore the namespace of elements
             (namespace,elemname) = get_elemname(elem.tag)
@@ -704,7 +766,7 @@
                 obj = reader.loads(elem)
                 view.add(obj)
             except exceptions.ConePersistenceError,e:
-                logging.getLogger('cone').warning("Could not parse element %s. Exception: %s" % (elem,e))
+                add_unknown_element_warning(elem)
                 continue
         return view
 
@@ -727,6 +789,9 @@
         """
         elem = ElementTree.Element('group', 
                                    {'name' : obj.get_name()})
+        if obj.get_id():
+            elem.set('id', obj.get_id())
+
         for child in obj._objects():
             """ Make sure that the object is mapped to an object in this model """
             mobj = child._get_mapper('confml').map_object(child)
@@ -758,6 +823,9 @@
         gname = elem.get('name')
         gref = utils.resourceref.to_dref(gname).replace('.','_')
         group = model.ConfmlGroup(gref, name=gname)
+        group.lineno = utils.etree.get_lineno(elem)
+        if elem.get('id'):
+            group.set_id(elem.get('id'))
         for elem in elem.getchildren():
             # At the moment we ignore the namespace of elements
             (namespace,elemname) = get_elemname(elem.tag)
@@ -767,58 +835,11 @@
                 if obj != None:
                     group.add(obj)
             except exceptions.ConePersistenceError,e:
-                logging.getLogger('cone').warning("Could not parse element %s. Exception: %s" % (elem,e))
+                add_unknown_element_warning(elem)
                 continue
         return group
 
 
-class GroupSettingWriter(ConfmlWriter):
-    """
-    """ 
-    @classmethod
-    def supported_class(cls, classname):
-        """
-        Class method to determine if this ConfmlWriter supports writing
-        of the given class name
-        """
-        if classname=="FeatureLink":
-            return True
-        else:
-            return False
-
-    def dumps(self, obj):
-        """
-        @param obj: The Configuration object 
-        """
-        ref = obj.fqr.replace('.','/')
-        elem = ElementTree.Element('setting', 
-                                   {'ref' : ref})
-        return elem
-
-class GroupSettingReader(ConfmlReader):
-    """
-    """ 
-    @classmethod
-    def supported_elem(cls, elemname, parent=None):
-        """
-        Class method to determine if this ConfmlWriter supports reading
-        of the given elem name
-        """
-        if elemname=='setting' and parent=='group':
-            return True
-        else:
-            return False
-
-    def loads(self, elem):
-        """
-        @param elem: The xml include elem
-        """
-        ref = elem.get('ref') or ''
-        ref = ref.replace('/','.')
-        return api.FeatureLink(ref)
-
-
-
 class ConfmlSettingWriter(ConfmlWriter):
     @classmethod
     def supported_class(cls, classname):
@@ -837,6 +858,7 @@
            classname=="ConfmlTimeSetting" or \
            classname=="ConfmlDateTimeSetting" or \
            classname=="ConfmlDurationSetting" or \
+           classname=="ConfmlHexBinarySetting" or \
            classname=="ConfmlFileSetting" or \
            classname=="ConfmlFolderSetting" or \
            classname=="FeatureSequence" or \
@@ -850,10 +872,20 @@
         @param obj: The Configuration object 
         """
         elem = ElementTree.Element('setting', 
-                                   {'ref' : obj.get_ref(),
-                                    'name' : obj.get_name()})
+                                   {'ref' : obj.get_ref()})
+        self._set_setting_properties(elem, obj)
+        return elem
+
+    def _set_setting_properties(self, elem, obj):
+        """
+        Set the setting properties for the given elm from the given feature object
+        """
+        if obj.get_name():
+            elem.set('name', obj.get_name())
         if obj.type:
             elem.set('type', obj.get_type())
+        if obj.get_id():
+            elem.set('id', obj.get_id())
         if hasattr(obj,'minOccurs'):
             elem.set('minOccurs', str(obj.minOccurs))
         if hasattr(obj,'maxOccurs'):
@@ -870,6 +902,8 @@
             elem.set('mapKey', str(obj.mapKey))
         if hasattr(obj,'mapValue') and obj.mapValue is not None:
             elem.set('mapValue', str(obj.mapValue))
+        if hasattr(obj,'displayName') and obj.displayName is not None:
+            elem.set('displayName', str(obj.displayName))
             
         for child in obj._objects():
             """ Make sure that the object is mapped to an object in this model """
@@ -878,7 +912,6 @@
             childelem = writer.dumps(child)
             if childelem != None:
                 elem.append(childelem)
-        return elem
 
 
 class ConfmlSettingReader(ConfmlReader):
@@ -891,7 +924,7 @@
         of the given elem name
         """
         if parent and not (parent=='feature' or parent=='setting'):
-             return False
+            return False
         if elemname=='setting':
             return True
         else:
@@ -905,7 +938,8 @@
         if typedef == 'sequence':
             map_key = elem.get('mapKey')
             map_value = elem.get('mapValue')
-            feature = model.ConfmlSequenceSetting(elem.get('ref'), mapKey=map_key, mapValue=map_value)
+            display_name = elem.get('displayName')
+            feature = model.ConfmlSequenceSetting(elem.get('ref'), mapKey=map_key, mapValue=map_value, displayName=display_name)
         elif typedef == 'int':
             feature = model.ConfmlIntSetting(elem.get('ref'))
         elif typedef == 'boolean':
@@ -930,14 +964,25 @@
             feature = model.ConfmlDateTimeSetting(elem.get('ref'))
         elif typedef == 'duration':
             feature = model.ConfmlDurationSetting(elem.get('ref'))
+        elif typedef == 'hexBinary':
+            feature = model.ConfmlHexBinarySetting(elem.get('ref'))
         
            
         else:
             # Handle the default setting as int type
-            feature = model.ConfmlSetting(elem.get('ref'), type=None)
+            feature = model.ConfmlSetting(elem.get('ref'), type=typedef)
+        feature.lineno = utils.etree.get_lineno(elem)
+        self._get_setting_properties(elem, feature)
+        return feature
         
+    def _get_setting_properties(self, elem, feature):
+        """
+        Get the setting properties for the given feature from the given xml elem
+        """
         if elem.get('name'):
             feature.set_name(elem.get('name'))
+        if elem.get('id'):
+            feature.set_id(elem.get('id'))
         if elem.get('minOccurs'):
             feature.minOccurs = int(elem.get('minOccurs'))
         if elem.get('maxOccurs'):
@@ -960,12 +1005,63 @@
                 if obj != None:
                     feature.add(obj,container.APPEND)
                 else:
-                    logging.getLogger('cone').warning("Invalid child %s in %s" % (elem,feature.name))
+                    add_parse_warning("Invalid child %s in %s" % (elem, feature.name),
+                                      utils.etree.get_lineno(elem))
             except exceptions.ConePersistenceError,e:
-                logging.getLogger('cone').warning("Could not parse element %s. Exception: %s" % (elem,e))
+                add_unknown_element_warning(elem)
                 continue
-        return feature
+
+
+class GroupSettingWriter(ConfmlSettingWriter):
+    """
+    """ 
+    @classmethod
+    def supported_class(cls, classname):
+        """
+        Class method to determine if this ConfmlWriter supports writing
+        of the given class name
+        """
+        if classname=="FeatureLink" or \
+           classname=="ConfmlFeatureLink":
+            return True
+        else:
+            return False
 
+    def dumps(self, obj):
+        """
+        @param obj: The Configuration object 
+        """
+        ref = obj.fqr.replace('.','/')
+        elem = ElementTree.Element('setting', 
+                                   {'ref' : ref})
+        self._set_setting_properties(elem, obj)
+        return elem
+
+
+class GroupSettingReader(ConfmlSettingReader):
+    """
+    """ 
+    @classmethod
+    def supported_elem(cls, elemname, parent=None):
+        """
+        Class method to determine if this ConfmlWriter supports reading
+        of the given elem name
+        """
+        if elemname=='setting' and parent=='group':
+            return True
+        else:
+            return False
+
+    def loads(self, elem):
+        """
+        @param elem: The xml include elem
+        """
+        ref = elem.get('ref') or ''
+        ref = ref.replace('/','.')
+        feature_link = model.ConfmlFeatureLink(ref)
+        feature_link.lineno = utils.etree.get_lineno(elem)
+        self._get_setting_properties(elem, feature_link)
+        return feature_link
 
 class ConfmlLocalPathWriter(ConfmlWriter):
     @classmethod
@@ -1007,7 +1103,9 @@
         """
         @param elem: The xml include elem
         """
-        return model.ConfmlLocalPath(readOnly=elem.get('readOnly'))
+        obj = model.ConfmlLocalPath(readOnly=elem.get('readOnly'))
+        obj.lineno = utils.etree.get_lineno(elem)
+        return obj
 
 
 class ConfmlTargetPathWriter(ConfmlWriter):
@@ -1050,7 +1148,9 @@
         """
         @param elem: The xml include elem
         """
-        return model.ConfmlTargetPath(readOnly=elem.get('readOnly'))
+        obj = model.ConfmlTargetPath(readOnly=elem.get('readOnly'))
+        obj.lineno = utils.etree.get_lineno(elem)
+        return obj
 
 
 class DummyWriter(ConfmlWriter):
@@ -1094,13 +1194,14 @@
         
         (namespace,elemname) = get_elemname(elem.tag)
         obj = api.DataContainer(elemname, container=True)
+        obj.lineno = utils.etree.get_lineno(elem)
         for elem in elem.getchildren():
             try:
                 reader = ElemReader(attr='rfs')
                 childobj = reader.loads(elem)
                 obj.add(childobj)
             except exceptions.ConePersistenceError,e:
-                logging.getLogger('cone').warning("Could not parse element %s. Exception: %s" % (elem,e))
+                add_unknown_element_warning(elem)
                 continue
         return obj
 
@@ -1118,10 +1219,15 @@
         datavalue = None
         if len(list(elem)) == 0:
             datavalue = elem.text
-        datatemplate = elem.get('template') == 'true' or self.template
-        dataextensionpolicy = elem.get('extensionPolicy') or ''
-        datamap = elem.get('map')
-        obj = api.Data(ref=elemname,value=datavalue, template=datatemplate, attr=self.attr,policy=dataextensionpolicy,map=datamap)
+        obj = model.ConfmlData(
+            ref      = elemname,
+            value    = datavalue,
+            template = elem.get('template') == 'true' or self.template,
+            attr     = self.attr,
+            policy   = elem.get('extensionPolicy') or '',
+            map      = elem.get('map'),
+            empty    = elem.get('empty') == 'true')
+        obj.lineno = utils.etree.get_lineno(elem)
         for elem in elem.getchildren():
             try:
                 reader = ElemReader(**self.args)
@@ -1152,13 +1258,13 @@
         
 
 def get_reader_for_elem(elemname, parent=None):
-    for reader in ConfmlReader.__subclasses__():
+    for reader in utils.all_subclasses(ConfmlReader):
         if reader.supported_elem(elemname,parent):
             return reader()
     raise exceptions.ConePersistenceError("No reader for given elem %s under %s found!" % (elemname, parent))
 
 def get_writer_for_class(classname):
-    for writer in ConfmlWriter.__subclasses__():
+    for writer in utils.all_subclasses(ConfmlWriter):
         if writer.supported_class(classname):
             return writer ()
     raise exceptions.ConePersistenceError("No writer for given class found! %s" % classname)