omf_common 6.0.0.pre.10 → 6.0.0.pre.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/bin/monitor_topic.rb +80 -0
  2. data/bin/send_create.rb +94 -0
  3. data/bin/send_request.rb +58 -0
  4. data/example/engine_alt.rb +136 -0
  5. data/example/vm_alt.rb +65 -0
  6. data/lib/omf_common.rb +224 -3
  7. data/lib/omf_common/comm.rb +113 -46
  8. data/lib/omf_common/comm/amqp/amqp_communicator.rb +76 -0
  9. data/lib/omf_common/comm/amqp/amqp_topic.rb +91 -0
  10. data/lib/omf_common/comm/local/local_communicator.rb +64 -0
  11. data/lib/omf_common/comm/local/local_topic.rb +42 -0
  12. data/lib/omf_common/comm/topic.rb +190 -0
  13. data/lib/omf_common/{dsl/xmpp.rb → comm/xmpp/communicator.rb} +93 -53
  14. data/lib/omf_common/comm/xmpp/topic.rb +147 -0
  15. data/lib/omf_common/{dsl → comm/xmpp}/xmpp_mp.rb +2 -0
  16. data/lib/omf_common/eventloop.rb +94 -0
  17. data/lib/omf_common/eventloop/em.rb +57 -0
  18. data/lib/omf_common/eventloop/local_evl.rb +78 -0
  19. data/lib/omf_common/message.rb +112 -229
  20. data/lib/omf_common/message/json/json_message.rb +129 -0
  21. data/lib/omf_common/message/xml/message.rb +410 -0
  22. data/lib/omf_common/message/xml/relaxng_schema.rb +17 -0
  23. data/lib/omf_common/message/xml/topic_message.rb +20 -0
  24. data/lib/omf_common/protocol/6.0.rnc +11 -21
  25. data/lib/omf_common/protocol/6.0.rng +52 -119
  26. data/lib/omf_common/version.rb +1 -1
  27. data/omf_common.gemspec +4 -2
  28. data/test/fixture/pubsub.rb +19 -19
  29. data/test/omf_common/{dsl/xmpp_spec.rb → comm/xmpp/communicator_spec.rb} +47 -111
  30. data/test/omf_common/comm/xmpp/topic_spec.rb +113 -0
  31. data/test/omf_common/comm_spec.rb +1 -0
  32. data/test/omf_common/message/xml/message_spec.rb +136 -0
  33. data/test/omf_common/message_spec.rb +37 -131
  34. data/test/test_helper.rb +4 -1
  35. metadata +38 -28
  36. data/lib/omf_common/core_ext/object.rb +0 -21
  37. data/lib/omf_common/relaxng_schema.rb +0 -17
  38. data/lib/omf_common/topic.rb +0 -34
  39. data/lib/omf_common/topic_message.rb +0 -20
  40. data/test/omf_common/topic_message_spec.rb +0 -114
  41. data/test/omf_common/topic_spec.rb +0 -75
@@ -0,0 +1,129 @@
1
+
2
+ require 'json'
3
+
4
+ module OmfCommon
5
+ class Message
6
+ class Json
7
+ class Message < OmfCommon::Message
8
+
9
+
10
+ def self.create(type, properties, body = {})
11
+ if type == :request
12
+ unless properties.kind_of?(Array)
13
+ raise "Expected array, but got #{properties.class} for request message"
14
+ end
15
+ properties = {select: properties}
16
+ elsif not properties.kind_of?(Hash)
17
+ raise "Expected hash, but got #{properties.class}"
18
+ end
19
+ content = body.merge({
20
+ operation: type,
21
+ mid: SecureRandom.uuid,
22
+ properties: properties
23
+ })
24
+ self.new(content)
25
+ end
26
+
27
+ def self.create_inform_message(itype = nil, properties = {}, body = {})
28
+ body[:itype] = itype if itype
29
+ create(:inform, properties, body)
30
+ end
31
+
32
+ # Create and return a message by parsing 'str'
33
+ #
34
+ def self.parse(str)
35
+ content = JSON.parse(str, :symbolize_names => true)
36
+ #puts content
37
+ new(content)
38
+ end
39
+
40
+ def each_property(&block)
41
+ @properties.each do |k, v|
42
+ #unless INTERNAL_PROPS.include?(k.to_sym)
43
+ block.call(k, v)
44
+ #end
45
+ end
46
+ end
47
+
48
+ def has_properties?
49
+ not @properties.empty?
50
+ end
51
+
52
+ def valid?
53
+ true # don't do schema verification , yet
54
+ end
55
+
56
+ # Loop over all the unbound (sent without a value) properties
57
+ # of a request message.
58
+ #
59
+ def each_unbound_request_property(&block)
60
+ unless type == :request
61
+ raise "Can only be used for request messages"
62
+ end
63
+ self[:select].each do |el|
64
+ #puts "UUU: #{el}::#{el.class}"
65
+ if el.is_a? Symbol
66
+ block.call(el)
67
+ end
68
+ end
69
+ end
70
+
71
+ # Loop over all the bound (sent with a value) properties
72
+ # of a request message.
73
+ #
74
+ def each_bound_request_property(&block)
75
+ unless type == :request
76
+ raise "Can only be used for request messages"
77
+ end
78
+ self[:select].each do |el|
79
+ #puts "BBB #{el}::#{el.class}"
80
+ if el.is_a? Hash
81
+ el.each do |key, value|
82
+ block.call(key, value)
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+
89
+ def to_s
90
+ "JsonMessage: #{@content.inspect}"
91
+ end
92
+
93
+ def marshall
94
+ @content.to_json
95
+ end
96
+
97
+ private
98
+ def initialize(content)
99
+ debug "Create message: #{content.inspect}"
100
+ @content = content
101
+ unless op = content[:operation]
102
+ raise "Missing message type (:operation)"
103
+ end
104
+ content[:operation] = op.to_sym # needs to be symbol
105
+ @properties = content[:properties] || []
106
+ #@properties = Hashie::Mash.new(content[:properties])
107
+ end
108
+
109
+ def _set_core(key, value)
110
+ @content[key] = value
111
+ end
112
+
113
+ def _get_core(key)
114
+ @content[key]
115
+ end
116
+
117
+ def _set_property(key, value)
118
+ @properties[key] = value
119
+ end
120
+
121
+ def _get_property(key)
122
+ #puts key
123
+ @properties[key]
124
+ end
125
+
126
+ end # class
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,410 @@
1
+ require 'niceogiri'
2
+ require 'hashie'
3
+ require 'securerandom'
4
+ require 'openssl'
5
+ require 'cgi'
6
+
7
+ require 'omf_common/message/xml/relaxng_schema'
8
+
9
+ module OmfCommon
10
+ class Message
11
+ class XML
12
+
13
+ # @example To create a valid omf message, e.g. a 'create' message:
14
+ #
15
+ # Message.create(:create,
16
+ # { p1: 'p1_value', p2: { unit: 'u', precision: 2 } },
17
+ # { guard: { p1: 'p1_value' } })
18
+ class Message < OmfCommon::Message
19
+ include Comparable
20
+
21
+ attr_accessor :xml
22
+ attr_accessor :content
23
+
24
+ class << self
25
+ # Create a OMF message
26
+ def create(operation_type, properties = {}, core_elements= {})
27
+ # For request messages, properties will be an array
28
+ if properties.kind_of? Array
29
+ properties = Hashie::Mash.new.tap do |mash|
30
+ properties.each { |p| mash[p] = nil }
31
+ end
32
+ end
33
+
34
+ properties = Hashie::Mash.new(properties)
35
+ core_elements = Hashie::Mash.new(core_elements)
36
+
37
+ if operation_type.to_sym == :create
38
+ core_elements[:rtype] ||= properties[:type]
39
+ end
40
+
41
+ content = core_elements.merge({
42
+ operation: operation_type,
43
+ type: operation_type,
44
+ properties: properties
45
+ })
46
+
47
+ new(content)
48
+ end
49
+
50
+ def parse(xml)
51
+ raise ArgumentError, 'Can not parse an empty XML into OMF message' if xml.nil? || xml.empty?
52
+
53
+ xml_node = Nokogiri::XML(xml).root
54
+
55
+ self.create(xml_node.name.to_sym).tap do |message|
56
+ message.xml = xml_node
57
+
58
+ message.send(:_set_core, :mid, message.xml.attr('mid'))
59
+
60
+ message.xml.elements.each do |el|
61
+ unless %w(digest props guard).include? el.name
62
+ message.send(:_set_core, el.name, message.read_content(el.name))
63
+ end
64
+
65
+ if el.name == 'props'
66
+ message.read_element('props').first.element_children.each do |prop_node|
67
+ message.send(:_set_property,
68
+ prop_node.element_name,
69
+ message.reconstruct_data(prop_node))
70
+ end
71
+ end
72
+
73
+ if el.name == 'guard'
74
+ message.read_element('guard').first.element_children.each do |guard_node|
75
+ message.guard ||= Hashie::Mash.new
76
+ message.guard[guard_node.element_name] = message.reconstruct_data(guard_node)
77
+ end
78
+ end
79
+ end
80
+
81
+ if OmfCommon::Measure.enabled? && !@@mid_list.include?(message.mid)
82
+ MPMessage.inject(Time.now.to_f, message.operation.to_s, message.mid, message.cid, message.to_s.gsub("\n",''))
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def resource
89
+ r_id = _get_property(:res_id)
90
+ OmfCommon::Comm::XMPP::Topic.create(r_id)
91
+ end
92
+
93
+ def itype
94
+ @content.itype.to_s.upcase.gsub(/_/, '.') unless @content.itype.nil?
95
+ end
96
+
97
+ def marshall
98
+ build_xml
99
+ @xml.to_xml
100
+ end
101
+
102
+ alias_method :to_xml, :marshall
103
+ alias_method :to_s, :marshall
104
+
105
+ def build_xml
106
+ @xml = Niceogiri::XML::Node.new(self.operation.to_s, nil, OMF_NAMESPACE)
107
+
108
+ @xml.write_attr(:mid, mid)
109
+
110
+ props_node = Niceogiri::XML::Node.new(:props)
111
+ guard_node = Niceogiri::XML::Node.new(:guard)
112
+
113
+ props_ns.each do |k, v|
114
+ props_node.add_namespace_definition(k.to_s, v.to_s)
115
+ end
116
+
117
+ @xml.add_child(props_node)
118
+ @xml.add_child(guard_node) if _get_core(:guard)
119
+
120
+ (OMF_CORE_READ - [:mid, :guard, :operation]).each do |attr|
121
+ attr_value = self.send(attr)
122
+
123
+ next unless attr_value
124
+
125
+ add_element(attr, attr_value) unless (self.operation != :release && attr == :res_id)
126
+ end
127
+
128
+ self.properties.each { |k, v| add_property(k, v) }
129
+ self.guard.each { |k, v| add_property(k, v, :guard) } if _get_core(:guard)
130
+
131
+ #digest = OpenSSL::Digest::SHA512.new(@xml.canonicalize)
132
+
133
+ #add_element(:digest, digest)
134
+ @xml
135
+ end
136
+
137
+ # Construct a property xml node
138
+ #
139
+ def add_property(key, value = nil, add_to = :props)
140
+ if !default_props_ns.empty? && add_to == :props
141
+ key_node = Niceogiri::XML::Node.new(key, nil, default_props_ns)
142
+ else
143
+ key_node = Niceogiri::XML::Node.new(key)
144
+ end
145
+
146
+ unless value.nil?
147
+ key_node.write_attr('type', ruby_type_2_prop_type(value.class))
148
+ c_node = value_node_set(value)
149
+
150
+ if c_node.class == Array
151
+ c_node.each { |c_n| key_node.add_child(c_n) }
152
+ else
153
+ key_node.add_child(c_node)
154
+ end
155
+ end
156
+ read_element(add_to).first.add_child(key_node)
157
+ key_node
158
+ end
159
+
160
+ def value_node_set(value, key = nil)
161
+ case value
162
+ when Hash
163
+ [].tap do |array|
164
+ value.each_pair do |k, v|
165
+ n = Niceogiri::XML::Node.new(k, nil, OMF_NAMESPACE)
166
+ n.write_attr('type', ruby_type_2_prop_type(v.class))
167
+
168
+ c_node = value_node_set(v, k)
169
+ if c_node.class == Array
170
+ c_node.each { |c_n| n.add_child(c_n) }
171
+ else
172
+ n.add_child(c_node)
173
+ end
174
+ array << n
175
+ end
176
+ end
177
+ when Array
178
+ value.map do |v|
179
+ n = Niceogiri::XML::Node.new('it', nil, OMF_NAMESPACE)
180
+ n.write_attr('type', ruby_type_2_prop_type(v.class))
181
+
182
+ c_node = value_node_set(v, 'it')
183
+ if c_node.class == Array
184
+ c_node.each { |c_n| n.add_child(c_n) }
185
+ else
186
+ n.add_child(c_node)
187
+ end
188
+ n
189
+ end
190
+ else
191
+ if key.nil?
192
+ string_value(value)
193
+ else
194
+ n = Niceogiri::XML::Node.new(key, nil, OMF_NAMESPACE)
195
+ n.add_child(string_value(value))
196
+ end
197
+ end
198
+ end
199
+
200
+ # Generate SHA1 of canonicalised xml and write into the ID attribute of the message
201
+ #
202
+ def sign
203
+ write_attr('mid', SecureRandom.uuid)
204
+ write_attr('ts', Time.now.utc.to_i)
205
+ canonical_msg = self.canonicalize
206
+
207
+ #priv_key = OmfCommon::Key.instance.private_key
208
+ #digest = OpenSSL::Digest::SHA512.new(canonical_msg)
209
+
210
+ #signature = Base64.encode64(priv_key.sign(digest, canonical_msg)).encode('utf-8') if priv_key
211
+ #write_attr('digest', digest)
212
+ #write_attr('signature', signature) if signature
213
+
214
+ if OmfCommon::Measure.enabled?
215
+ MPMessage.inject(Time.now.to_f, operation.to_s, mid, cid, self.to_s.gsub("\n",''))
216
+ @@mid_list << mid
217
+ end
218
+ self
219
+ end
220
+
221
+ # Validate against relaxng schema
222
+ #
223
+ def valid?
224
+ build_xml
225
+
226
+ validation = RelaxNGSchema.instance.validate(@xml.document)
227
+ if validation.empty?
228
+ true
229
+ else
230
+ logger.error validation.map(&:message).join("\n")
231
+ logger.debug @xml.to_s
232
+ false
233
+ end
234
+ end
235
+
236
+ # Short cut for adding xml node
237
+ #
238
+ def add_element(key, value = nil, &block)
239
+ key_node = Niceogiri::XML::Node.new(key)
240
+ @xml.add_child(key_node)
241
+ if block
242
+ block.call(key_node)
243
+ else
244
+ key_node.content = value if value
245
+ end
246
+ key_node
247
+ end
248
+
249
+ # Short cut for grabbing a group of nodes using xpath, but with default namespace
250
+ def element_by_xpath_with_default_namespace(xpath_without_ns)
251
+ xpath_without_ns = xpath_without_ns.to_s
252
+ if !default_props_ns.empty? && xpath_without_ns !~ /props|guard|ts|src|mid|rtype|res_id|cid|itype/
253
+ @xml.xpath(xpath_without_ns.gsub(/(^|\/{1,2})(\w+)/, "\\1#{rtype.to_s}:\\2"), default_props_ns)
254
+ else
255
+ @xml.xpath(xpath_without_ns.gsub(/(^|\/{1,2})(\w+)/, '\1xmlns:\2'), :xmlns => OMF_NAMESPACE)
256
+ end
257
+ end
258
+
259
+ # In case you think method :element_by_xpath_with_default_namespace is too long
260
+ #
261
+ alias_method :read_element, :element_by_xpath_with_default_namespace
262
+
263
+ # We just want to know the content of an non-repeatable element
264
+ #
265
+ def read_content(element_name)
266
+ element_content = read_element("#{element_name}").first.content rescue nil
267
+ unless element_content.nil?
268
+ element_content.empty? ? nil : element_content
269
+ else
270
+ nil
271
+ end
272
+ end
273
+
274
+ # Reconstruct xml node into Ruby object
275
+ #
276
+ # @param [Niceogiri::XML::Node] property xml node
277
+ # @return [Object] the content of the property, as string, integer, float, or mash(hash with indifferent access)
278
+ def reconstruct_data(node, data_binding = nil)
279
+ node_type = node.attr('type')
280
+ case node_type
281
+ when 'array'
282
+ node.element_children.map do |child|
283
+ reconstruct_data(child, data_binding)
284
+ end
285
+ when /hash/
286
+ mash ||= Hashie::Mash.new
287
+ node.element_children.each do |child|
288
+ mash[child.attr('key') || child.element_name] ||= reconstruct_data(child, data_binding)
289
+ end
290
+ mash
291
+ when /boolean/
292
+ node.content == "true"
293
+ else
294
+ if node.content.empty?
295
+ nil
296
+ elsif data_binding && node_type == 'string'
297
+ ERB.new(node.content).result(data_binding)
298
+ else
299
+ node.content.ducktype
300
+ end
301
+ end
302
+ end
303
+
304
+ def <=>(another)
305
+ @content <=> another.content
306
+ end
307
+
308
+ def properties
309
+ @content.properties
310
+ end
311
+
312
+ def has_properties?
313
+ @content.properties.empty?
314
+ end
315
+
316
+ def guard?
317
+ @content.guard.empty?
318
+ end
319
+
320
+ # Pretty print for application event message
321
+ #
322
+ def print_app_event
323
+ "APP_EVENT (#{read_property(:app)}, ##{read_property(:seq)}, #{read_property(:event)}): #{read_property(:msg)}"
324
+ end
325
+
326
+ # Iterate each property key value pair
327
+ #
328
+ def each_property(&block)
329
+ properties.each { |k, v| block.call(k, v) }
330
+ end
331
+
332
+
333
+ def each_unbound_request_property(&block)
334
+ raise "Can only be used for request messages. Got #{type}." if type != :request
335
+ properties.each { |k, v| block.call(k, v) if v.nil? }
336
+ end
337
+
338
+ def each_bound_request_property(&block)
339
+ raise "Can only be used for request messages. Got #{type}." if type != :request
340
+ properties.each { |k, v| block.call(k, v) unless v.nil? }
341
+ end
342
+
343
+ #def [](name, evaluate = false)
344
+ # value = properties[name]
345
+
346
+ # if evaluate && value.kind_of?(String)
347
+ # ERB.new(value).result(evaluate)
348
+ # else
349
+ # value
350
+ # end
351
+ #end
352
+
353
+ alias_method :read_property, :[]
354
+
355
+ alias_method :write_property, :[]=
356
+
357
+ private
358
+
359
+ def initialize(content = {})
360
+ @content = content
361
+ @content.mid = SecureRandom.uuid
362
+ @content.ts = Time.now.utc.to_i
363
+ end
364
+
365
+ def _set_core(key, value)
366
+ @content[key] = value
367
+ end
368
+
369
+ def _get_core(key)
370
+ @content[key]
371
+ end
372
+
373
+ def _set_property(key, value, ns = nil)
374
+ # TODO what to do here
375
+ @content.properties[key] = value
376
+ end
377
+
378
+ def _get_property(key, ns = nil)
379
+ # TODO what to do here
380
+ @content.properties[key]
381
+ end
382
+
383
+ def ruby_type_2_prop_type(ruby_class_type)
384
+ v_type = ruby_class_type.to_s.downcase
385
+ case v_type
386
+ when *%w(trueclass falseclass)
387
+ 'boolean'
388
+ when *%w(fixnum bignum)
389
+ 'integer'
390
+ when /hash|mash/
391
+ 'hash'
392
+ else
393
+ v_type
394
+ end
395
+ end
396
+
397
+ # Get string of a value object, escape if object is string
398
+ def string_value(value)
399
+ if value.kind_of? String
400
+ value = CGI::escape_html(value)
401
+ else
402
+ value = value.to_s
403
+ end
404
+ value
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end
410
+