#
# 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:
#
import logging
from cone.public import api, exceptions, container, utils
from cone.confml import model
from cone.validation.confmlvalidation import ValidatorBase, FixerBase
class SettingValidatorBase(ValidatorBase):
"""
Base class for validators that validate ConfML settings
(sub-classes of cone.confml.model.ConfmlSetting).
"""
def validate(self):
for ref, feature in self.context.feature_dict.iteritems():
if isinstance(feature._obj, model.ConfmlSetting):
self.validate_setting(ref, feature)
def validate_setting(self, ref, setting):
raise NotImplementedError()
class LengthConstraintValidator(SettingValidatorBase):
"""
Validator for validating xs:length, xs:minLength and xs:maxLength
constraints on setting data values.
"""
PROBLEM_TYPES = ['model.confml.invalid_value.length',
'model.confml.invalid_value.minlength',
'model.confml.invalid_value.maxlength']
def validate_setting(self, ref, setting):
if setting.length:
value = setting.get_value()
if isinstance(value, basestring) and len(value) != setting.length:
self._add_problem(
setting = setting,
msg = "Setting %s: Exact number of characters must be %s (value has %s)" % (ref, setting.length, len(value)),
prob_type = self.PROBLEM_TYPES[0])
if setting.minLength:
value = setting.get_value()
if isinstance(value, basestring) and len(value) < setting.minLength:
self._add_problem(
setting = setting,
msg = "Setting %s: Minimum number of characters is %s (value has %s)" % (ref, setting.minLength, len(value)),
prob_type = self.PROBLEM_TYPES[1])
if setting.maxLength:
value = setting.get_value()
if isinstance(value, basestring) and len(value) > setting.maxLength:
self._add_problem(
setting = setting,
msg = "Setting %s: Maximum number of characters is %s (value has %s)" % (ref, setting.maxLength, len(value)),
prob_type = self.PROBLEM_TYPES[2])
def _add_problem(self, setting, msg, prob_type):
dataobj = setting.datas['data'][-1]
prob = api.Problem(
msg = msg,
type = prob_type,
line = dataobj.lineno,
file = dataobj.get_configuration_path())
self.context.problems.append(prob)
class MissingFeatureForDataValidator(ValidatorBase):
"""
Validator for validating data elements that do not have a
corresponding feature/setting in the configuration.
"""
PROBLEM_TYPES = ['model.confml.missing_feature_for_data']
def validate(self):
for dataobj in self.context.configuration._traverse(type=api.Data):
try:
self.context.dview.get_feature(dataobj.fqr)
except exceptions.NotFound:
prob = api.Problem(
msg = "Feature '%s' not found" % dataobj.fqr,
type = self.PROBLEM_TYPES[0],
line = dataobj.lineno,
file = dataobj.get_configuration_path())
self.context.problems.append(prob)
class MissingDescriptionValidator(SettingValidatorBase):
"""
Validator for validating missing descriptions in feature/setting in the configuration.
"""
PROBLEM_TYPES = ['model.confml.missing_desc']
def validate_setting(self, ref, setting):
print 'Validating missing desc!'
if not setting.desc or setting.desc == '':
prob = api.Problem(
msg = "Setting/Feature %s: has no description" % (ref),
type = self.PROBLEM_TYPES[0],
line = setting.lineno,
file = setting.get_configuration_path(),
severity = api.Problem.SEVERITY_WARNING)
self.context.problems.append(prob)
class DuplicateSettingValidator(ValidatorBase):
"""
Validator for validating that there are no settings with same ref in given
configuration.
"""
PROBLEM_TYPES = ['model.confml.duplicate.setting']
def validate(self):
settings_container = container.DataContainer()
# Traverse through the configuration model and store each feature to
# the settings_container.
for setting in self.context.configuration._traverse(type=model.ConfmlSetting):
settings_container.add_value(setting.fqr, setting)
# Go though the settings_container to see if any features have more than one
# definition and report those as problems
for fqr in settings_container.list_keys():
if len(settings_container.get_values(fqr)) > 1:
files = [setting.get_configuration_path() for setting in settings_container.get_values(fqr)]
prob = api.Problem(
msg = "Feature %s has '%s' definitions in files %s" % (fqr, len(settings_container.get_values(fqr)), files),
type = self.PROBLEM_TYPES[0],
severity = api.Problem.SEVERITY_WARNING,
line = settings_container.get_value(fqr).lineno,
file = files[-1],
problem_data = settings_container.get_values(fqr))
self.context.problems.append(prob)
class DuplicateFeatureValidator(ValidatorBase):
"""
Validator for validating that there are no features with same ref in given
configuration.
"""
PROBLEM_TYPES = ['model.confml.duplicate.feature']
def validate(self):
settings_container = container.DataContainer()
# Traverse through the configuration model and store each feature to
# the settings_container.
for setting in self.context.configuration._traverse(type=model.ConfmlFeature):
settings_container.add_value(setting.fqr, setting)
# Go though the settings_container to see if any features have more than one
# definition and report those as problems
for fqr in settings_container.list_keys():
if len(settings_container.get_values(fqr)) > 1:
files = [setting.get_configuration_path() for setting in settings_container.get_values(fqr)]
prob = api.Problem(
msg = "Feature %s has '%s' definitions in files %s" % (fqr, len(settings_container.get_values(fqr)), files),
type = self.PROBLEM_TYPES[0],
severity = api.Problem.SEVERITY_INFO,
line = settings_container.get_value(fqr).lineno,
file = files[-1],
problem_data = settings_container.get_values(fqr))
self.context.problems.append(prob)
class DuplicateSettingFixer(FixerBase):
"""
A Fix class for duplicate settings that removes all but the last definition of the element.
"""
PROBLEM_TYPES = ['model.confml.duplicate.setting']
def fix(self, context):
for problem in self.filter_problems(context.problems, self.PROBLEM_TYPES[0]):
logging.getLogger('cone.validation').info("Fixing problem %s" % problem.msg)
context.fixes.append("Fixed problem: %s" % problem.msg)
# The problem data is expected to have those duplicate settings and the
# actual setting as a last element
for setting in problem.problem_data[0:-1]:
parent_fea = setting.find_parent(type=api.Feature)
logging.getLogger('cone.validation').info("Remove setting %s from %s" % (setting.fqr, parent_fea.get_configuration_path()))
try:
parent_fea.remove_feature(setting.ref)
except exceptions.NotFound:
logging.getLogger('cone.validation').info("Already removed %s from %s" % (setting.ref, parent_fea.get_configuration_path()))
class DuplicateFeatureFixer(FixerBase):
"""
A Fix class for duplicate features that merges all setting under a duplicate feature
to the first instance of the feature and removes the duplicates.
"""
PROBLEM_TYPES = ['model.confml.duplicate.feature']
def fix(self, context):
for problem in self.filter_problems(context.problems, self.PROBLEM_TYPES[0]):
logging.getLogger('cone.validation').info("Fixing problem %s" % problem.msg)
context.fixes.append("Fixed problem: %s" % problem.msg)
# The problem data is expected to have those duplicate settings and the
# actual setting as a last element
target_feature = problem.problem_data[0]
target_config = target_feature.find_parent(type=api.Configuration)
for feature in problem.problem_data[1:]:
logging.getLogger('cone.validation').info("Move settings from Feature %s in %s to %s" % \
(feature.fqr, feature.get_configuration_path(), target_feature.get_configuration_path()))
for setting_ref in feature.list_features():
setting = feature.get_feature(setting_ref)
# Get the path from feature to the parent of this setting
# (pathto,ref) = utils.dottedref.psplit_ref(setting_ref)
if target_feature.has_ref(setting_ref):
target_feature.remove_feature(setting_ref)
target_feature.add_feature(setting)
config = feature.find_parent(type=api.Configuration)
logging.getLogger('cone.validation').info("Remove feature %s from %s" % (feature.fqr, config.get_full_path()))
config.remove_feature(feature.ref)
#: List of all built-in ConfML validator classes
VALIDATOR_CLASSES = [
MissingFeatureForDataValidator,
LengthConstraintValidator,
DuplicateSettingValidator,
DuplicateFeatureValidator,
# MissingDescriptionValidator,
]
#: List of all built-in ConfML fixer classes
FIXER_CLASSES = [
DuplicateFeatureFixer,
]