|
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 logging |
|
18 |
|
19 from cone.public import api, exceptions, container, utils |
|
20 from cone.confml import model |
|
21 |
|
22 from cone.validation.confmlvalidation import ValidatorBase, FixerBase |
|
23 |
|
24 class SettingValidatorBase(ValidatorBase): |
|
25 """ |
|
26 Base class for validators that validate ConfML settings |
|
27 (sub-classes of cone.confml.model.ConfmlSetting). |
|
28 """ |
|
29 def validate(self): |
|
30 for ref, feature in self.context.feature_dict.iteritems(): |
|
31 if isinstance(feature._obj, model.ConfmlSetting): |
|
32 self.validate_setting(ref, feature) |
|
33 |
|
34 def validate_setting(self, ref, setting): |
|
35 raise NotImplementedError() |
|
36 |
|
37 class LengthConstraintValidator(SettingValidatorBase): |
|
38 """ |
|
39 Validator for validating xs:length, xs:minLength and xs:maxLength |
|
40 constraints on setting data values. |
|
41 """ |
|
42 PROBLEM_TYPES = ['model.confml.invalid_value.length', |
|
43 'model.confml.invalid_value.minlength', |
|
44 'model.confml.invalid_value.maxlength'] |
|
45 |
|
46 def validate_setting(self, ref, setting): |
|
47 if setting.length: |
|
48 value = setting.get_value() |
|
49 if isinstance(value, basestring) and len(value) != setting.length: |
|
50 self._add_problem( |
|
51 setting = setting, |
|
52 msg = "Setting %s: Exact number of characters must be %s (value has %s)" % (ref, setting.length, len(value)), |
|
53 prob_type = self.PROBLEM_TYPES[0]) |
|
54 |
|
55 if setting.minLength: |
|
56 value = setting.get_value() |
|
57 if isinstance(value, basestring) and len(value) < setting.minLength: |
|
58 self._add_problem( |
|
59 setting = setting, |
|
60 msg = "Setting %s: Minimum number of characters is %s (value has %s)" % (ref, setting.minLength, len(value)), |
|
61 prob_type = self.PROBLEM_TYPES[1]) |
|
62 |
|
63 if setting.maxLength: |
|
64 value = setting.get_value() |
|
65 if isinstance(value, basestring) and len(value) > setting.maxLength: |
|
66 self._add_problem( |
|
67 setting = setting, |
|
68 msg = "Setting %s: Maximum number of characters is %s (value has %s)" % (ref, setting.maxLength, len(value)), |
|
69 prob_type = self.PROBLEM_TYPES[2]) |
|
70 |
|
71 def _add_problem(self, setting, msg, prob_type): |
|
72 dataobj = setting.datas['data'][-1] |
|
73 prob = api.Problem( |
|
74 msg = msg, |
|
75 type = prob_type, |
|
76 line = dataobj.lineno, |
|
77 file = dataobj.get_configuration_path()) |
|
78 self.context.problems.append(prob) |
|
79 |
|
80 class MissingFeatureForDataValidator(ValidatorBase): |
|
81 """ |
|
82 Validator for validating data elements that do not have a |
|
83 corresponding feature/setting in the configuration. |
|
84 """ |
|
85 PROBLEM_TYPES = ['model.confml.missing_feature_for_data'] |
|
86 |
|
87 def validate(self): |
|
88 for dataobj in self.context.configuration._traverse(type=api.Data): |
|
89 try: |
|
90 self.context.dview.get_feature(dataobj.fqr) |
|
91 except exceptions.NotFound: |
|
92 prob = api.Problem( |
|
93 msg = "Feature '%s' not found" % dataobj.fqr, |
|
94 type = self.PROBLEM_TYPES[0], |
|
95 line = dataobj.lineno, |
|
96 file = dataobj.get_configuration_path()) |
|
97 self.context.problems.append(prob) |
|
98 |
|
99 class MissingDescriptionValidator(SettingValidatorBase): |
|
100 """ |
|
101 Validator for validating missing descriptions in feature/setting in the configuration. |
|
102 """ |
|
103 PROBLEM_TYPES = ['model.confml.missing_desc'] |
|
104 |
|
105 def validate_setting(self, ref, setting): |
|
106 print 'Validating missing desc!' |
|
107 if not setting.desc or setting.desc == '': |
|
108 prob = api.Problem( |
|
109 msg = "Setting/Feature %s: has no description" % (ref), |
|
110 type = self.PROBLEM_TYPES[0], |
|
111 line = setting.lineno, |
|
112 file = setting.get_configuration_path(), |
|
113 severity = api.Problem.SEVERITY_WARNING) |
|
114 self.context.problems.append(prob) |
|
115 |
|
116 class DuplicateSettingValidator(ValidatorBase): |
|
117 """ |
|
118 Validator for validating that there are no settings with same ref in given |
|
119 configuration. |
|
120 """ |
|
121 PROBLEM_TYPES = ['model.confml.duplicate.setting'] |
|
122 |
|
123 def validate(self): |
|
124 settings_container = container.DataContainer() |
|
125 # Traverse through the configuration model and store each feature to |
|
126 # the settings_container. |
|
127 for setting in self.context.configuration._traverse(type=model.ConfmlSetting): |
|
128 settings_container.add_value(setting.fqr, setting) |
|
129 # Go though the settings_container to see if any features have more than one |
|
130 # definition and report those as problems |
|
131 for fqr in settings_container.list_keys(): |
|
132 if len(settings_container.get_values(fqr)) > 1: |
|
133 files = [setting.get_configuration_path() for setting in settings_container.get_values(fqr)] |
|
134 prob = api.Problem( |
|
135 msg = "Feature %s has '%s' definitions in files %s" % (fqr, len(settings_container.get_values(fqr)), files), |
|
136 type = self.PROBLEM_TYPES[0], |
|
137 severity = api.Problem.SEVERITY_WARNING, |
|
138 line = settings_container.get_value(fqr).lineno, |
|
139 file = files[-1], |
|
140 problem_data = settings_container.get_values(fqr)) |
|
141 self.context.problems.append(prob) |
|
142 |
|
143 class DuplicateFeatureValidator(ValidatorBase): |
|
144 """ |
|
145 Validator for validating that there are no features with same ref in given |
|
146 configuration. |
|
147 """ |
|
148 PROBLEM_TYPES = ['model.confml.duplicate.feature'] |
|
149 |
|
150 def validate(self): |
|
151 settings_container = container.DataContainer() |
|
152 # Traverse through the configuration model and store each feature to |
|
153 # the settings_container. |
|
154 for setting in self.context.configuration._traverse(type=model.ConfmlFeature): |
|
155 settings_container.add_value(setting.fqr, setting) |
|
156 # Go though the settings_container to see if any features have more than one |
|
157 # definition and report those as problems |
|
158 for fqr in settings_container.list_keys(): |
|
159 if len(settings_container.get_values(fqr)) > 1: |
|
160 files = [setting.get_configuration_path() for setting in settings_container.get_values(fqr)] |
|
161 prob = api.Problem( |
|
162 msg = "Feature %s has '%s' definitions in files %s" % (fqr, len(settings_container.get_values(fqr)), files), |
|
163 type = self.PROBLEM_TYPES[0], |
|
164 severity = api.Problem.SEVERITY_INFO, |
|
165 line = settings_container.get_value(fqr).lineno, |
|
166 file = files[-1], |
|
167 problem_data = settings_container.get_values(fqr)) |
|
168 self.context.problems.append(prob) |
|
169 |
|
170 class DuplicateSettingFixer(FixerBase): |
|
171 """ |
|
172 A Fix class for duplicate settings that removes all but the last definition of the element. |
|
173 """ |
|
174 PROBLEM_TYPES = ['model.confml.duplicate.setting'] |
|
175 |
|
176 def fix(self, context): |
|
177 for problem in self.filter_problems(context.problems, self.PROBLEM_TYPES[0]): |
|
178 logging.getLogger('cone.validation').info("Fixing problem %s" % problem.msg) |
|
179 context.fixes.append("Fixed problem: %s" % problem.msg) |
|
180 # The problem data is expected to have those duplicate settings and the |
|
181 # actual setting as a last element |
|
182 for setting in problem.problem_data[0:-1]: |
|
183 parent_fea = setting.find_parent(type=api.Feature) |
|
184 logging.getLogger('cone.validation').info("Remove setting %s from %s" % (setting.fqr, parent_fea.get_configuration_path())) |
|
185 try: |
|
186 parent_fea.remove_feature(setting.ref) |
|
187 except exceptions.NotFound: |
|
188 logging.getLogger('cone.validation').info("Already removed %s from %s" % (setting.ref, parent_fea.get_configuration_path())) |
|
189 |
|
190 class DuplicateFeatureFixer(FixerBase): |
|
191 """ |
|
192 A Fix class for duplicate features that merges all setting under a duplicate feature |
|
193 to the first instance of the feature and removes the duplicates. |
|
194 """ |
|
195 PROBLEM_TYPES = ['model.confml.duplicate.feature'] |
|
196 |
|
197 def fix(self, context): |
|
198 for problem in self.filter_problems(context.problems, self.PROBLEM_TYPES[0]): |
|
199 logging.getLogger('cone.validation').info("Fixing problem %s" % problem.msg) |
|
200 context.fixes.append("Fixed problem: %s" % problem.msg) |
|
201 # The problem data is expected to have those duplicate settings and the |
|
202 # actual setting as a last element |
|
203 target_feature = problem.problem_data[0] |
|
204 target_config = target_feature.find_parent(type=api.Configuration) |
|
205 for feature in problem.problem_data[1:]: |
|
206 logging.getLogger('cone.validation').info("Move settings from Feature %s in %s to %s" % \ |
|
207 (feature.fqr, feature.get_configuration_path(), target_feature.get_configuration_path())) |
|
208 for setting_ref in feature.list_features(): |
|
209 setting = feature.get_feature(setting_ref) |
|
210 # Get the path from feature to the parent of this setting |
|
211 # (pathto,ref) = utils.dottedref.psplit_ref(setting_ref) |
|
212 if target_feature.has_ref(setting_ref): |
|
213 target_feature.remove_feature(setting_ref) |
|
214 target_feature.add_feature(setting) |
|
215 |
|
216 config = feature.find_parent(type=api.Configuration) |
|
217 logging.getLogger('cone.validation').info("Remove feature %s from %s" % (feature.fqr, config.get_full_path())) |
|
218 config.remove_feature(feature.ref) |
|
219 |
|
220 |
|
221 #: List of all built-in ConfML validator classes |
|
222 VALIDATOR_CLASSES = [ |
|
223 MissingFeatureForDataValidator, |
|
224 LengthConstraintValidator, |
|
225 DuplicateSettingValidator, |
|
226 DuplicateFeatureValidator, |
|
227 # MissingDescriptionValidator, |
|
228 ] |
|
229 |
|
230 #: List of all built-in ConfML fixer classes |
|
231 FIXER_CLASSES = [ |
|
232 DuplicateFeatureFixer, |
|
233 ] |