configurationengine/source/cone/public/_plugin_reader.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
equal deleted inserted replaced
-1:000000000000 0:2e8eeb919028
       
     1 #
       
     2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 # All rights reserved.
       
     4 # This component and the accompanying materials are made available
       
     5 # under the terms of "Eclipse Public License v1.0"
       
     6 # which accompanies this distribution, and is available
       
     7 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 #
       
     9 # Initial Contributors:
       
    10 # Nokia Corporation - initial contribution.
       
    11 #
       
    12 # Contributors:
       
    13 #
       
    14 # Description:
       
    15 #
       
    16 
       
    17 import copy
       
    18 import logging
       
    19 import plugin, exceptions, api, utils
       
    20 import cone.confml.model
       
    21 
       
    22 log = logging.getLogger('cone')
       
    23 
       
    24 # The XML namespace for common ImplML definitions
       
    25 COMMON_IMPLML_NAMESPACE = "http://www.symbianfoundation.org/xml/implml/1"
       
    26 
       
    27 # Name of the marker variable used to mark a feature as a temporary
       
    28 # feature
       
    29 TEMP_FEATURE_MARKER_VARNAME = '__plugin_temp_feature_marker'
       
    30 
       
    31 class TempVariableDefinition(object):
       
    32     """
       
    33     Class representing a temporary variable definition in an implementation file.
       
    34     """
       
    35     
       
    36     def __init__(self, ref, type, value):
       
    37         self.ref = ref
       
    38         self.type = type
       
    39         self.value = value
       
    40     
       
    41     def create_feature(self, config):
       
    42         """
       
    43         Add a feature based on this temp feature definition to the given configuration.
       
    44         """
       
    45         if '.' in self.ref:
       
    46             pos = self.ref.rfind('.')
       
    47             ref = self.ref[pos + 1:]
       
    48             namespace = self.ref[:pos]
       
    49         else:
       
    50             ref = self.ref
       
    51             namespace = ''
       
    52         
       
    53         mapping = {'string' : cone.confml.model.ConfmlStringSetting,
       
    54                    'int'    : cone.confml.model.ConfmlIntSetting,
       
    55                    'real'   : cone.confml.model.ConfmlRealSetting,
       
    56                    'boolean': cone.confml.model.ConfmlBooleanSetting}
       
    57         feature = mapping[self.type](ref)
       
    58         setattr(feature, TEMP_FEATURE_MARKER_VARNAME, True)
       
    59         config.add_feature(feature, namespace)
       
    60         
       
    61         value = utils.expand_refs_by_default_view(self.value, config.get_default_view())
       
    62         config.add_data(api.Data(fqr=self.ref, value=value))
       
    63     
       
    64     def __eq__(self, other):
       
    65         if type(self) is type(other):
       
    66             for varname in ('ref', 'type', 'value'):
       
    67                 if getattr(self, varname) != getattr(other, varname):
       
    68                     return False
       
    69             return True
       
    70         else:
       
    71             return False
       
    72         
       
    73     def __ne__(self, other):
       
    74         return not (self == other)
       
    75     
       
    76     def __repr__(self):
       
    77         return "TempFeatureDefinition(ref=%r, type=%r, value=%r)" % (self.ref, self.type, self.value)
       
    78 
       
    79 class TempVariableSequenceDefinition(object):
       
    80     """
       
    81     Class representing a temporary variable sequence definition in an implementation file.
       
    82     """
       
    83     
       
    84     def __init__(self, ref, sub_items):
       
    85         self.ref = ref
       
    86         self.sub_items = sub_items
       
    87     
       
    88     def create_feature(self, config):
       
    89         if '.' in self.ref:
       
    90             pos = self.ref.rfind('.')
       
    91             ref = self.ref[pos + 1:]
       
    92             namespace = self.ref[:pos]
       
    93         else:
       
    94             ref = self.ref
       
    95             namespace = ''
       
    96         
       
    97         # Creature the sequence feature
       
    98         seq_fea = api.FeatureSequence(ref)
       
    99         setattr(seq_fea, TEMP_FEATURE_MARKER_VARNAME, True)
       
   100         config.add_feature(seq_fea, namespace)
       
   101         
       
   102         # Create the sub-features
       
   103         mapping = {'string' : cone.confml.model.ConfmlStringSetting,
       
   104                    'int'    : cone.confml.model.ConfmlIntSetting,
       
   105                    'real'   : cone.confml.model.ConfmlRealSetting,
       
   106                    'boolean': cone.confml.model.ConfmlBooleanSetting}
       
   107         sub_features = []
       
   108         for sub_item in self.sub_items:
       
   109             sub_feature = mapping[sub_item[1]](sub_item[0])
       
   110             seq_fea.add_feature(sub_feature)
       
   111     
       
   112     def __eq__(self, other):
       
   113         if type(self) is type(other):
       
   114             return self.ref == other.ref and self.sub_items == other.sub_items
       
   115         else:
       
   116             return False
       
   117         
       
   118     def __ne__(self, other):
       
   119         return not (self == other)
       
   120     
       
   121     def __repr__(self):
       
   122         return "TempSeqFeatureDefinition(ref=%r, sub_items=%r)" % (self.ref, self.sub_items)
       
   123 
       
   124 class SettingRefsOverride(object):
       
   125     """
       
   126     Class representing a setting reference override for an implementation.
       
   127     """
       
   128     def __init__(self, refs=None):
       
   129         """
       
   130         @param refs: The reference overrides, can be a list of references or None.
       
   131         """
       
   132         self.refs = refs
       
   133     
       
   134     def get_refs(self):
       
   135         return self.refs
       
   136 
       
   137     def __eq__(self, other):
       
   138         if type(self) is type(other):
       
   139             return self.refs == other.refs
       
   140         else:
       
   141             return False
       
   142         
       
   143     def __ne__(self, other):
       
   144         return not (self == other)
       
   145     
       
   146     def __repr__(self):
       
   147         return "SettingRefsOverride(refs=%r)" % self.refs
       
   148 
       
   149 class CommonImplmlData(object):
       
   150     """
       
   151     Class representing the common ImplML namespace data read from
       
   152     an XML element.
       
   153     """
       
   154     
       
   155     def __init__(self):
       
   156         self.phase = None
       
   157         self.tags = None
       
   158         self.tempvar_defs = []
       
   159         self.setting_refs_override = None
       
   160         self.output_root_dir = None
       
   161         self.output_sub_dir = None
       
   162     
       
   163     def apply(self, impl):
       
   164         """
       
   165         Apply the data on the given implementation instance.
       
   166         """
       
   167         if self.phase:
       
   168             impl.set_invocation_phase(self.phase)
       
   169         if self.tags:
       
   170             impl.set_tags(self.tags)
       
   171         if self.setting_refs_override:
       
   172             # Override the get_refs() method of the implementation
       
   173             impl.get_refs = self.setting_refs_override.get_refs
       
   174             # Override also the has_ref() method in case it is overridden
       
   175             # in the implementation sub-class
       
   176             impl.has_ref = lambda refs: plugin.ImplBase.has_ref(impl, refs)
       
   177         if self.output_root_dir:
       
   178             impl.set_output_root_override(self.output_root_dir)
       
   179         if self.output_sub_dir:
       
   180             impl.output_subdir = self.output_sub_dir
       
   181     
       
   182     def extend(self, other):
       
   183         """
       
   184         Extend this object with the contents of another CommonImplmlData object.
       
   185         """
       
   186         if other.phase:
       
   187             self.phase = other.phase
       
   188         if other.tags:
       
   189             self.tags = other.tags
       
   190         self.tempvar_defs.extend(other.tempvar_defs)
       
   191         if other.setting_refs_override:
       
   192             self.setting_refs_override = other.setting_refs_override
       
   193         if other.output_root_dir:
       
   194             self.output_root_dir = other.output_root_dir
       
   195         if other.output_sub_dir:
       
   196             self.output_sub_dir = other.output_sub_dir
       
   197     
       
   198     def copy(self):
       
   199         result = CommonImplmlData()
       
   200         result.phase = self.phase
       
   201         if result.tags is not None:
       
   202             result.tags = self.tags.copy()
       
   203         result.tempvar_defs = list(self.tempvar_defs)
       
   204         result.setting_refs_override = copy.deepcopy(self.setting_refs_override)
       
   205         result.output_root_dir = self.output_root_dir
       
   206         result.output_sub_dir = self.output_sub_dir
       
   207         return result
       
   208     
       
   209     def __eq__(self, other):
       
   210         if type(self) is type(other):
       
   211             for varname in ('phase', 'tags', 'tempvar_defs', 'setting_refs_override', 'output_root_dir', 'output_sub_dir'):
       
   212                 if getattr(self, varname) != getattr(other, varname):
       
   213                     return False
       
   214             return True
       
   215         else:
       
   216             return False
       
   217     
       
   218     def __ne__(self, other):
       
   219         return not (self == other)
       
   220     
       
   221     def __repr__(self):
       
   222         return "CommonImplmlData(phase=%r, tags=%r, tempvar_defs=%r, setting_refs_override=%r, output_root_dir=%r, output_sub_dir=%r)" \
       
   223             % (self.phase,
       
   224                self.tags,
       
   225                self.tempvar_defs,
       
   226                self.setting_refs_override,
       
   227                self.output_root_dir,
       
   228                self.output_sub_dir)
       
   229 
       
   230 class ImplReader(object):
       
   231     """
       
   232     Internal reader class for reading implementations from a file in a configuration.
       
   233     """
       
   234     
       
   235     # The reader class list loaded using ImplFactory
       
   236     __loaded_reader_classes = None
       
   237     __reader_classes = None
       
   238     __supported_file_extensions = None
       
   239     __ignored_namespaces = None
       
   240     
       
   241     def __init__(self, resource_ref, configuration):
       
   242         self.resource_ref = resource_ref
       
   243         self.configuration = configuration
       
   244     
       
   245     @classmethod
       
   246     def _load_data_from_plugins(cls):
       
   247         """
       
   248         Load all data needed for implementation parsing from the plug-ins.
       
   249         
       
   250         The actual loading is only done the first time this method is called.
       
   251         """
       
   252         # Load the data only if the reader class list has not been loaded
       
   253         # yet or it has changed
       
   254         loaded_reader_classes = plugin.ImplFactory.get_reader_classes()
       
   255         if cls.__loaded_reader_classes is loaded_reader_classes:
       
   256             return
       
   257         
       
   258         reader_classes = [plugin.ReaderBase]
       
   259         reader_classes.extend(loaded_reader_classes)
       
   260         
       
   261         cls.__reader_classes = {}
       
   262         cls.__ignored_namespaces = []
       
   263         cls.__supported_file_extensions = []
       
   264         
       
   265         for rc in reader_classes:
       
   266             # Reader class
       
   267             ns = rc.NAMESPACE
       
   268             if ns is not None:
       
   269                 if ns in cls.__reader_classes:
       
   270                     raise RuntimeError("Multiple reader classes registered for ImplML namespace '%s': at least %s and %s"\
       
   271                                        % (ns, rc, cls.__reader_classes[ns]))
       
   272                 cls.__reader_classes[ns] = rc
       
   273             
       
   274             # Ignored namespaces
       
   275             for ns in rc.IGNORED_NAMESPACES:
       
   276                 if ns not in cls.__ignored_namespaces:
       
   277                     cls.__ignored_namespaces.append(ns)
       
   278             
       
   279             # Supported file extensions
       
   280             for fe in rc.FILE_EXTENSIONS:
       
   281                 fe = fe.lower()
       
   282                 if fe not in cls.__supported_file_extensions:
       
   283                     cls.__supported_file_extensions.append(fe)
       
   284             
       
   285         cls.__loaded_reader_classes = loaded_reader_classes
       
   286     
       
   287     @classmethod
       
   288     def _get_namespaces(cls, etree):
       
   289         """
       
   290         Return a list of XML namespaces in the given element tree.
       
   291         """
       
   292         namespaces = []
       
   293         namespaces.append(utils.xml.split_tag_namespace(etree.tag)[0])
       
   294         for elem in etree:
       
   295             ns = utils.xml.split_tag_namespace(elem.tag)[0]
       
   296             if ns not in namespaces:
       
   297                 namespaces.append(ns)
       
   298         return filter(lambda ns: ns is not None, namespaces)
       
   299     
       
   300     def _read_impls_from_file_root_element(self, root, namespaces):
       
   301         impls = []
       
   302         reader_classes = self.get_reader_classes()
       
   303         
       
   304         # Go through the list of XML namespaces encountered in the
       
   305         # file and read an implementation using the corresponding
       
   306         # reader for each namespace
       
   307         impl_count = 0
       
   308         common_data = CommonImplmlDataReader.read_data(root)
       
   309         for ns in namespaces:
       
   310             if ns not in reader_classes: continue
       
   311             
       
   312             rc = reader_classes[ns]
       
   313             impl = self._read_impl(rc, root)
       
   314             if impl:
       
   315                 impl.index = impl_count
       
   316                 impl_count += 1
       
   317                 if common_data: common_data.apply(impl)
       
   318                 impls.append(impl)
       
   319         
       
   320         # Add temp feature definitions to the first implementation
       
   321         if common_data and impls:
       
   322             impls[0]._tempvar_defs.extend(common_data.tempvar_defs)
       
   323         return impls
       
   324     
       
   325     def _read_impls_from_file_sub_elements(self, root):
       
   326         impls = []
       
   327         
       
   328         # Collect common ImplML namespace data
       
   329         common_data = CommonImplmlData()
       
   330         for elem in root:
       
   331             ns = utils.xml.split_tag_namespace(elem.tag)[0]
       
   332             if ns == COMMON_IMPLML_NAMESPACE:
       
   333                 cd = CommonImplmlDataReader.read_data(elem)
       
   334                 if cd: common_data.extend(cd)
       
   335         
       
   336         # Go through all sub-elements and read an implementation instance
       
   337         # from each if possible
       
   338         impl_count = 0
       
   339         reader_classes = self.get_reader_classes()
       
   340         for elem in root:
       
   341             ns = utils.xml.split_tag_namespace(elem.tag)[0]
       
   342             if ns != COMMON_IMPLML_NAMESPACE and ns in reader_classes:
       
   343                 reader_class = reader_classes[ns]
       
   344                 impl = self._read_impl(reader_class, elem)
       
   345                 if impl:
       
   346                     cd = CommonImplmlDataReader.read_data(elem)
       
   347                     if cd is not None:
       
   348                         impl._tempvar_defs.extend(cd.tempvar_defs)
       
   349                         data = common_data.copy()
       
   350                         data.extend(cd)
       
   351                         data.apply(impl)
       
   352                     else:
       
   353                         common_data.apply(impl)
       
   354                     
       
   355                     impl.index = impl_count
       
   356                     impl_count += 1
       
   357                     impls.append(impl)
       
   358         
       
   359         # Add temporary feature definitions to the first implementation instance
       
   360         if impls:
       
   361             impls[0]._tempvar_defs = common_data.tempvar_defs + impls[0]._tempvar_defs
       
   362         
       
   363         return impls
       
   364     
       
   365     def _read_impl(self, reader_class, elem):
       
   366         """
       
   367         Read an implementation with the given reader class from the given element.
       
   368         
       
   369         If an exception is raised during reading, the exception is logged
       
   370         and None returned. 
       
   371         
       
   372         @return: The read implementation or None.
       
   373         """
       
   374         try:
       
   375             return reader_class.read_impl(self.resource_ref, self.configuration, elem)
       
   376         except exceptions.ParseError, e:
       
   377             log.error("Error reading implementation '%s': %s", (self.resource_ref, e))
       
   378         except Exception, e:
       
   379             utils.log_exception(log, e)
       
   380             
       
   381         return None
       
   382 
       
   383     @classmethod
       
   384     def get_reader_classes(cls):
       
   385         """
       
   386         Return a dictionary of all possible implementation reader classes.
       
   387         
       
   388         Dictionary key is the XML namespace and the value is the corresponding
       
   389         reader class.
       
   390         """
       
   391         cls._load_data_from_plugins()
       
   392         return cls.__reader_classes
       
   393     
       
   394     @classmethod
       
   395     def get_supported_file_extensions(cls):
       
   396         """
       
   397         Return a list of all supported implementation file extensions.
       
   398         """
       
   399         cls._load_data_from_plugins()
       
   400         return cls.__supported_file_extensions
       
   401     
       
   402     @classmethod
       
   403     def get_ignored_namespaces(cls):
       
   404         """
       
   405         Return a list of all ignored XML namespaces.
       
   406         """
       
   407         cls._load_data_from_plugins()
       
   408         return cls.__ignored_namespaces
       
   409 
       
   410     def read_implementations(self):
       
   411         try:
       
   412             root = plugin.ReaderBase._read_xml_doc_from_resource(self.resource_ref, self.configuration)
       
   413             return self.read_implementation(root)
       
   414         except exceptions.ParseError, e:
       
   415             # Invalid XML data in the file
       
   416             log.error(e)
       
   417             return []
       
   418 
       
   419     def read_implementation(self, xmlroot):
       
   420         root = xmlroot
       
   421         
       
   422         # Check if the implementations should all be read from the
       
   423         # document root, or each from its own sub-element under the root
       
   424         read_from_root = False
       
   425         ns = utils.xml.split_tag_namespace(root.tag)[0]
       
   426         if ns: read_from_root = True
       
   427         
       
   428         # Collect namespaces from the file and check that all are supported or ignored
       
   429         namespaces = self._get_namespaces(root)
       
   430         for ns in namespaces:
       
   431             if ns != COMMON_IMPLML_NAMESPACE \
       
   432                 and ns not in self.get_reader_classes() \
       
   433                 and ns not in self.get_ignored_namespaces():
       
   434                 log.error("Unsupported XML namespace '%s' in file '%s'" % (ns, self.resource_ref))
       
   435                 return []
       
   436         
       
   437         if read_from_root:
       
   438             impls = self._read_impls_from_file_root_element(root, namespaces)
       
   439         else:
       
   440             impls = self._read_impls_from_file_sub_elements(root)
       
   441         return impls
       
   442 
       
   443 
       
   444 class CommonImplmlDataReader(object):
       
   445     """
       
   446     Internal reader class for reading common ImplML namespace data from and element.
       
   447     """
       
   448     
       
   449     VALID_PHASES = ('pre', 'normal', 'post')
       
   450     VALID_TYPES = ('string', 'int', 'real', 'boolean')
       
   451     
       
   452     @classmethod
       
   453     def read_data(cls, etree):
       
   454         """
       
   455         Read common ImplML data from the given XML element.
       
   456         @return: A CommonImplmlData instance or None if no common namespace
       
   457             elements were found.
       
   458         """
       
   459         result = CommonImplmlData()
       
   460         
       
   461         reader_methods = {'phase'                   : cls._read_phase,
       
   462                           'tag'                     : cls._read_tag,
       
   463                           'tempVariable'            : cls._read_tempvar,
       
   464                           'tempVariableSequence'    : cls._read_tempvarseq,
       
   465                           'settingRefsOverride'     : cls._read_setting_refs_override,
       
   466                           'outputRootDir'           : cls._read_output_root_dir,
       
   467                           'outputSubDir'            : cls._read_output_sub_dir}
       
   468         
       
   469         found = False
       
   470         for elem in etree:
       
   471             ns, tag = utils.xml.split_tag_namespace(elem.tag)
       
   472             if ns != COMMON_IMPLML_NAMESPACE:   continue
       
   473             if tag not in reader_methods:       continue
       
   474             
       
   475             reader_methods[tag](elem, result)
       
   476             found = True
       
   477         
       
   478         if found:   return result
       
   479         else:       return None
       
   480     
       
   481     @classmethod
       
   482     def _read_phase(cls, elem, result):
       
   483         phase = elem.get('name')
       
   484         if phase is None:
       
   485             cls._raise_missing_attr(elem, 'name')
       
   486         if phase not in cls.VALID_PHASES:
       
   487             raise exceptions.ParseError("Invalid invocation phase '%s' defined." % phase)
       
   488         
       
   489         result.phase = phase
       
   490     
       
   491     @classmethod
       
   492     def _read_tag(cls, elem, result):
       
   493         name = elem.get('name')
       
   494         value = elem.get('value')
       
   495         if name is not None:
       
   496             if result.tags is None:     result.tags = {}
       
   497             if name not in result.tags: result.tags[name] = []
       
   498             result.tags[name].append(value)
       
   499     
       
   500     @classmethod
       
   501     def _read_tempvar(cls, elem, result):
       
   502         ref = elem.get('ref')
       
   503         type = elem.get('type', 'string')
       
   504         value = elem.get('value', '')
       
   505         
       
   506         if ref is None:
       
   507             cls._raise_missing_attr(elem, 'ref')
       
   508         if type not in cls.VALID_TYPES:
       
   509             cls._raise_invalid_type(ref, type)
       
   510         
       
   511         result.tempvar_defs.append(TempVariableDefinition(ref, type, value))
       
   512     
       
   513     @classmethod
       
   514     def _read_tempvarseq(cls, elem, result):
       
   515         ref = elem.get('ref')
       
   516         if ref is None:
       
   517             cls._raise_missing_attr(elem, 'ref')
       
   518         
       
   519         sub_items = []
       
   520         for sub_elem in elem.findall('{%s}tempVariable' % COMMON_IMPLML_NAMESPACE):
       
   521             sub_ref = sub_elem.get('ref')
       
   522             sub_type = sub_elem.get('type', 'string')
       
   523             
       
   524             if sub_ref is None:
       
   525                 cls._raise_missing_attr(sub_elem, 'ref')
       
   526             if sub_type not in cls.VALID_TYPES:
       
   527                 cls._raise_invalid_type(sub_ref, sub_type)
       
   528             
       
   529             sub_items.append((sub_ref, sub_type))
       
   530         
       
   531         if not sub_items:
       
   532             raise exceptions.ParseError("Temporary variable sequence '%s' does not have any sub-items" % ref)
       
   533         
       
   534         result.tempvar_defs.append(TempVariableSequenceDefinition(ref, sub_items))
       
   535     
       
   536     @classmethod
       
   537     def _read_setting_refs_override(cls, elem, result):
       
   538         if elem.get('refsIrrelevant', 'false').lower() in ('1', 'true'):
       
   539             refs = None
       
   540         else:
       
   541             refs = []
       
   542             for sub_elem in elem.findall('{%s}settingRef' % COMMON_IMPLML_NAMESPACE):
       
   543                 ref = sub_elem.get('value')
       
   544                 
       
   545                 if ref is None:
       
   546                     cls._raise_missing_attr(sub_elem, 'value')
       
   547                 
       
   548                 refs.append(ref)
       
   549                 
       
   550         result.setting_refs_override = SettingRefsOverride(refs)
       
   551     
       
   552     @classmethod
       
   553     def _read_output_root_dir(cls, elem, result):
       
   554         value = elem.get('value')
       
   555         if value: result.output_root_dir = value
       
   556     
       
   557     @classmethod
       
   558     def _read_output_sub_dir(cls, elem, result):
       
   559         value = elem.get('value')
       
   560         if value: result.output_sub_dir = value
       
   561 
       
   562     @classmethod
       
   563     def _raise_missing_attr(cls, elem, attrname):
       
   564         raise exceptions.ParseError("XML element %s does not contain the mandatory '%s' attribute." % (elem.tag, attrname))
       
   565     
       
   566     @classmethod
       
   567     def _raise_invalid_type(cls, ref, type):
       
   568         raise exceptions.ParseError("Invalid feature type '%s' specified for temporary ConfML feature '%s'." % (type, ref))