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

Sign up to get free protection for your applications and to get access to all the features.
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
+