|
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 Legacy RuleML plug-in to support RuleML 1 and 2 after rule engine changes |
|
18 in cone.public.rules. The new RuleML version is 3, and it uses the setting |
|
19 reference format ${MyFeature.MySetting} in the rules. |
|
20 |
|
21 The rule engine (rules.py) before the change is contained in this plug-in. |
|
22 |
|
23 NOTE THAT THIS PLUG-IN WILL NOT BE MAINTAINED, ALL NEW DEVELOPMENT SHOULD |
|
24 HAPPEN IN THE NEW RULE PLUG-IN. IF NEW FUNCTIONALITY IS REQUIRED IN AN |
|
25 EXISTING RULEML FILE, THE CHANGES SHOULD BE MADE TO THE NEW RULE PLUG-IN |
|
26 AND THE EXISTING RULEML FILE CONVERTED TO RULEML 3. |
|
27 ''' |
|
28 |
|
29 |
|
30 import os |
|
31 import sys |
|
32 import logging |
|
33 import shutil |
|
34 import pkg_resources |
|
35 |
|
36 import __init__ |
|
37 import re |
|
38 |
|
39 from legacyruleplugin import relations, rules |
|
40 from cone.public import exceptions,plugin,utils,api |
|
41 |
|
42 class RuleImpl(plugin.ImplBase): |
|
43 """ |
|
44 MakeImpl plugin finds feature references that are configured in a .ruleml file |
|
45 and generate a rule from them |
|
46 """ |
|
47 IMPL_TYPE_ID = 'ruleml' |
|
48 DEFAULT_INVOCATION_PHASE = 'pre' |
|
49 |
|
50 def __init__(self, ref, configuration, relation_container): |
|
51 """ |
|
52 Overloading the default constructor |
|
53 """ |
|
54 plugin.ImplBase.__init__(self,ref,configuration) |
|
55 self.relation_container = relation_container |
|
56 |
|
57 def list_output_files(self): |
|
58 """ |
|
59 Return a list of output files as an array. |
|
60 """ |
|
61 return [] |
|
62 |
|
63 def generate(self, context=None): |
|
64 relation_container = self.get_relation_container() |
|
65 relation_container.context = context |
|
66 return relation_container.execute(context) |
|
67 |
|
68 def has_tag(self, tags, policy=None): |
|
69 # RuleML should always be executed, regardless of the tags |
|
70 return True |
|
71 |
|
72 def get_relation_container(self): |
|
73 return self.relation_container |
|
74 |
|
75 def get_refs(self): |
|
76 """ |
|
77 Return a list of all ConfML setting references that affect this |
|
78 implementation. May also return None if references are not relevant |
|
79 for the implementation. |
|
80 """ |
|
81 refs = [] |
|
82 relations = self.get_relations() |
|
83 for relation in relations: |
|
84 # get refs from relation return a tuple (left side refs, right side refs) |
|
85 # only the left side refs are the "input" refs |
|
86 refs += relation.get_refs() |
|
87 # If the rules do not have any references return None to disable filter by refs |
|
88 if refs == []: |
|
89 refs = None |
|
90 return refs |
|
91 |
|
92 def get_relations(self): |
|
93 return self.relation_container.get_relations() |
|
94 |
|
95 class RuleBuiltinsModule(object): |
|
96 pass |
|
97 |
|
98 class RulemlRelationContainer(plugin.RelationContainer): |
|
99 """ |
|
100 Relation container for RuleML rules. |
|
101 |
|
102 Basically this is a wrapper for rules.RelationContainer that adapts |
|
103 it to the interface of plugin.RelationContainer. |
|
104 """ |
|
105 def __init__(self, configuration, source, rule_list, eval_globals): |
|
106 plugin.RelationContainer.__init__(self, configuration, source=source) |
|
107 self.configuration = configuration |
|
108 self.relation_container = rules.RelationContainerImpl() |
|
109 self.eval_globals = eval_globals |
|
110 self.context = None |
|
111 for rule in rule_list: |
|
112 self.relation_container.add_relation(rule) |
|
113 |
|
114 def execute(self, context=None): |
|
115 results = [] |
|
116 |
|
117 # Create the autoconfig if not done already |
|
118 autoconfig = plugin.get_autoconfig(self.configuration) |
|
119 |
|
120 # Register relations etc. to the rule engine. |
|
121 # Due to unit test issues the relations are not registered |
|
122 # in the relations module, but only for the duration of |
|
123 # rule parsing and execution |
|
124 relations.register() |
|
125 try: |
|
126 # Using the configuration to pass the eval globals dict to the |
|
127 # eval expression. The configuration only contains the globals |
|
128 # dict for the duration of the rule execution, so hopefully this |
|
129 # shouldn't mess anything up |
|
130 self._set_builtin_eval_globals() |
|
131 context._eval_expression_globals_dict = self.eval_globals |
|
132 for i, rel in enumerate(self.relation_container): |
|
133 index = i + 1 |
|
134 |
|
135 # Execute |
|
136 self._execute_relation_and_log_error(rel, self.source, index, context) |
|
137 |
|
138 # Collect execution result if supported |
|
139 if hasattr(rel, 'get_execution_result'): |
|
140 result = rel.get_execution_result() |
|
141 if isinstance(result, plugin.RelationExecutionResult): |
|
142 result.source = self.source |
|
143 result.index = index |
|
144 results.append(result) |
|
145 |
|
146 del context._eval_expression_globals_dict |
|
147 |
|
148 if self.relation_container.has_errors(): |
|
149 for error in self.relation_container.get_errors(): |
|
150 logging.getLogger('cone.ruleml_relation_container(%s)' % self.source).error(error) |
|
151 |
|
152 if self.context: |
|
153 self.context.results += results |
|
154 self.context.add_changed_refs(autoconfig.list_leaf_datas()) |
|
155 return results |
|
156 finally: |
|
157 relations.unregister() |
|
158 |
|
159 def get_relation_count(self): |
|
160 return len(self.relation_container) |
|
161 |
|
162 def get_relations(self): |
|
163 return list(self.relation_container) |
|
164 |
|
165 def _set_builtin_eval_globals(self): |
|
166 """ |
|
167 Add built-in attributes into the eval globals dictionary. |
|
168 """ |
|
169 |
|
170 builtins = RuleBuiltinsModule() |
|
171 builtins.configuration = self.configuration |
|
172 |
|
173 self.eval_globals['ruleml'] = builtins |
|
174 |
|
175 class RuleImplReaderBase(plugin.ReaderBase): |
|
176 NAMESPACE = None # Used as a base class, so should have no namespace |
|
177 FILE_EXTENSIONS = ['ruleml'] |
|
178 |
|
179 def __init__(self, resource_ref, configuration): |
|
180 self.resource_ref = resource_ref |
|
181 self.configuration = configuration |
|
182 |
|
183 @classmethod |
|
184 def read_impl(cls, resource_ref, configuration, etree): |
|
185 reader = cls(resource_ref, configuration) |
|
186 |
|
187 # Register relations etc. to the rule engine. |
|
188 # Due to unit test issues the relations are not registered |
|
189 # in the relations module, but only for the duration of |
|
190 # rule parsing and execution |
|
191 relations.register() |
|
192 try: |
|
193 rules = reader.parse_rules(resource_ref, etree) |
|
194 eval_globals = reader.parse_eval_globals(etree) |
|
195 lineno = utils.etree.get_lineno(etree) |
|
196 |
|
197 # Create an ImplContainer to hold each rule as its own |
|
198 # RuleML implementation |
|
199 main_impl = plugin.ImplContainer(resource_ref, configuration) |
|
200 main_impl.lineno = lineno |
|
201 |
|
202 for rule in rules: |
|
203 relation_container = RulemlRelationContainer( |
|
204 configuration = configuration, |
|
205 source = "%s:%d" % (resource_ref, rule.lineno), |
|
206 rule_list = [rule], |
|
207 eval_globals = eval_globals) |
|
208 |
|
209 impl = RuleImpl(resource_ref, configuration, relation_container) |
|
210 impl.lineno = rule.lineno |
|
211 rule.implml = impl |
|
212 |
|
213 main_impl.append(impl) |
|
214 finally: |
|
215 relations.unregister() |
|
216 |
|
217 return main_impl |
|
218 |
|
219 class RuleImplReader1(RuleImplReaderBase): |
|
220 NAMESPACE = 'http://www.s60.com/xml/ruleml/1' |
|
221 NAMESPACE_ID = 'ruleml1' |
|
222 ROOT_ELEMENT_NAME = 'ruleml' |
|
223 |
|
224 def __init__(self, resource_ref, configuration): |
|
225 RuleImplReaderBase.__init__(self, resource_ref, configuration) |
|
226 |
|
227 @classmethod |
|
228 def get_schema_data(cls): |
|
229 return pkg_resources.resource_string('legacyruleplugin', 'xsd/ruleml.xsd') |
|
230 |
|
231 def parse_rules(self, ref, etree): |
|
232 rules = [] |
|
233 for elem in etree.getiterator("{%s}rule" % self.NAMESPACE): |
|
234 lineno = utils.etree.get_lineno(elem) |
|
235 for rule in relations.RelationFactory.get_relations(self.configuration, elem.text): |
|
236 rule.ref = ref |
|
237 rule.lineno = lineno |
|
238 rules.append(rule) |
|
239 return rules |
|
240 |
|
241 def parse_eval_globals(self, etree): |
|
242 return {} |
|
243 |
|
244 class RuleImplReader2(RuleImplReaderBase): |
|
245 NAMESPACE = 'http://www.s60.com/xml/ruleml/2' |
|
246 NAMESPACE_ID = 'ruleml2' |
|
247 ROOT_ELEMENT_NAME = 'ruleml' |
|
248 |
|
249 def __init__(self, resource_ref, configuration): |
|
250 RuleImplReaderBase.__init__(self, resource_ref, configuration) |
|
251 |
|
252 @classmethod |
|
253 def get_schema_data(cls): |
|
254 return pkg_resources.resource_string('legacyruleplugin', 'xsd/ruleml2.xsd') |
|
255 |
|
256 def parse_rules(self, ref, etree): |
|
257 rules = [] |
|
258 for elem in etree.getiterator("{%s}rule" % self.NAMESPACE): |
|
259 lineno = utils.etree.get_lineno(elem) |
|
260 for rule in relations.RelationFactory.get_relations(self.configuration, self._replace_eval_blocks(elem.text)): |
|
261 rule.ref = ref |
|
262 rule.lineno = lineno |
|
263 rules.append(rule) |
|
264 return rules |
|
265 |
|
266 def parse_eval_globals(self, etree): |
|
267 eval_globals = {} |
|
268 for elem in etree.getiterator("{%s}eval_globals" % self.NAMESPACE): |
|
269 text = "" |
|
270 if elem.get('file') != None: |
|
271 self._read_eval_globals_from_file(elem.get('file'), eval_globals) |
|
272 else: |
|
273 try: |
|
274 # Strip surrounding whitespace, otherwise there might be Python |
|
275 # indentation errors |
|
276 text = elem.text.strip() |
|
277 exec(text, eval_globals) |
|
278 except Exception, e: |
|
279 logging.getLogger('cone.ruleml(%s)' % self.resource_ref).warning('Failed to evaluate eval_globals block, exception: %s' % (e)) |
|
280 return eval_globals |
|
281 |
|
282 def _read_eval_globals_from_file(self, relative_path, eval_globals): |
|
283 # Get the actual path (relative to the current implementation file) |
|
284 base_path = os.path.dirname(self.resource_ref) |
|
285 pyfile_path = os.path.normpath(os.path.join(base_path, relative_path)).replace('\\', '/') |
|
286 # Read the data and execute |
|
287 try: |
|
288 resource = None |
|
289 resource = self.configuration.get_resource(pyfile_path) |
|
290 text = resource.read() |
|
291 exec(text.replace('\r', ''), eval_globals) |
|
292 except Exception, e: |
|
293 logging.getLogger('cone.ruleml(%s)' % self.resource_ref).warning('Cannot import eval file: %s. Exception: %s' % (pyfile_path, e)) |
|
294 finally: |
|
295 if resource is not None: resource.close() |
|
296 |
|
297 |
|
298 @classmethod |
|
299 def _replace_eval_blocks(cls, code): |
|
300 return utils.expand_delimited_tokens( |
|
301 string = code, |
|
302 expander_func = lambda ref, index: '__eval__ %r' % ref, |
|
303 delimiters =('{%', '%}')) |