class OmfCommon::Message::XML::Message
@example To create a valid omf message, e.g. a ‘create’ message:
Message.create(:create, { p1: 'p1_value', p2: { unit: 'u', precision: 2 } }, { guard: { p1: 'p1_value' } })
Attributes
content[RW]
xml[RW]
Public Class Methods
create(operation_type, properties = {}, core_elements= {})
click to toggle source
Create a OMF message
# File lib/omf_common/message/xml/message.rb, line 30 def create(operation_type, properties = {}, core_elements= {}) # For request messages, properties will be an array if properties.kind_of? Array properties = Hashie::Mash.new.tap do |mash| properties.each { |p| mash[p] = nil } end end properties = Hashie::Mash.new(properties) core_elements = Hashie::Mash.new(core_elements) if operation_type.to_sym == :create core_elements[:rtype] ||= properties[:type] end content = core_elements.merge({ operation: operation_type, type: operation_type, properties: properties }) issuer = self.authenticate? ? (core_elements[:issuer] || core_elements[:src]) : nil new(content, issuer) end
new(content = {}, issuer = nil)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 430 def initialize(content = {}, issuer = nil) @content = content @content.mid = SecureRandom.uuid @content.ts = Time.now.utc.to_i if (src = content[:src]) @content.src = OmfCommon.comm.create_topic(src) end @issuer = issuer @content.issuer = @issuer # keep track if we sent local certs on a topic. Should do this the first time @certOnTopic = {} end
parse(xml, content_type = "text/xml", &block)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 56 def parse(xml, content_type = "text/xml", &block) raise ArgumentError, 'Need message handling block' unless block content_type ||= "text/xml" # Since by default parent class pass in nil object raise ArgumentError, "Unknown content type: #{content_type}" unless content_type =~ /xml/ raise ArgumentError, 'Can not parse an empty XML into OMF message' if xml.nil? || xml.empty? xml_node = Nokogiri::XML(xml).root if xml_node.name.to_sym == :env # envelope cert = xml_node.element_children.find { |v| v.element_name == 'cert' }.content sig = xml_node.element_children.find { |v| v.element_name == 'sig' }.content iss = xml_node.element_children.find { |v| v.element_name == 'iss' }.content xml_node = xml_node.element_children.find { |v| v.element_name =~ /create|request|configure|release|inform/ } if self.authenticate? pem = "#{OmfCommon::Auth::Certificate::BEGIN_CERT}#{cert}#{OmfCommon::Auth::Certificate::END_CERT}" cert = OmfCommon::Auth::Certificate.create_from_pem(pem) cert.resource_id = iss if cert.nil? warn "Missing certificate of '#{iss}'" return nil end unless OmfCommon::Auth::CertificateStore.instance.verify(cert) warn "Invalid certificate '#{cert.to_s}', NOT signed by CA certs, or its CA cert NOT loaded into cert store." return nil end OmfCommon::Auth::CertificateStore.instance.register(cert) canonicalised_xml_node = fix_canonicalised_xml(xml_node.canonicalize) unless cert.to_x509.public_key.verify(OpenSSL::Digest::SHA256.new(canonicalised_xml_node), Base64.decode64(sig), canonicalised_xml_node) warn "Verfication failed #{canonicalised_xml_node} #{OpenSSL::Digest::SHA256.new(canonicalised_xml_node)}" return nil end end else if self.authenticate? debug "Message not signed: '#{xml}'" return nil end end parsed_msg = self.create(xml_node.name.to_sym, {}, { issuer: cert }).tap do |message| message.xml = xml_node message.send(:_set_core, :mid, message.xml.attr('mid')) message.xml.elements.each do |el| unless %w(digest props guard).include? el.name message.send(:_set_core, el.name, message.read_content(el.name)) end if el.name == 'props' message.read_element('props').first.element_children.each do |prop_node| e_name = prop_node.element_name if (ns_prefix = prop_node.namespace.prefix) e_name = "#{ns_prefix}__#{e_name}" end message.send(:_set_property, e_name, message.reconstruct_data(prop_node)) end end if el.name == 'guard' message.read_element('guard').first.element_children.each do |guard_node| message.guard ||= Hashie::Mash.new message.guard[guard_node.element_name] = message.reconstruct_data(guard_node) end end end if OmfCommon::Measure.enabled? MPMessage.inject(Time.now.to_f, message.content.operation.to_s, message.content.mid, message.content.cid, message.content.to_s.gsub("\n",'')) end end block.call(parsed_msg) parsed_msg end
Private Class Methods
fix_canonicalised_xml(str)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 497 def self.fix_canonicalised_xml(str) str.gsub(/\n +/, '').gsub(/ xmlns=\"\"/, '') end
Public Instance Methods
<=>(another)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 376 def <=>(another) @content <=> another.content end
add_element(key, value = nil, &block)
click to toggle source
Short cut for adding xml node
# File lib/omf_common/message/xml/message.rb, line 309 def add_element(key, value = nil, &block) key = escape_key(key) key_node = Niceogiri::XML::Node.new(key) @xml.add_child(key_node) if block block.call(key_node) else key_node.content = value if value end key_node end
add_property(key, value = nil, add_to = :props)
click to toggle source
Construct a property xml node
# File lib/omf_common/message/xml/message.rb, line 226 def add_property(key, value = nil, add_to = :props) key = escape_key(key) if !props_ns.empty? && add_to == :props && key =~ /^(.+)__(.+)$/ key_node = Niceogiri::XML::Node.new($2, nil, { $1 => props_ns[$1] }) else key_node = Niceogiri::XML::Node.new(key) end unless value.nil? key_node.write_attr('type', ruby_type_2_prop_type(value.class)) c_node = value_node_set(value) if c_node.class == Array c_node.each { |c_n| key_node.add_child(c_n) } else key_node.add_child(c_node) end end read_element(add_to).first.add_child(key_node) key_node end
build_xml()
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 188 def build_xml @xml = Niceogiri::XML::Node.new(self.operation.to_s, nil, OMF_NAMESPACE) @xml.write_attr(:mid, mid) props_node = Niceogiri::XML::Node.new(:props) guard_node = Niceogiri::XML::Node.new(:guard) props_ns.each do |k, v| props_node.add_namespace_definition(k, v.to_s) end @xml.add_child(props_node) @xml.add_child(guard_node) if _get_core(:guard) (OMF_CORE_READ - [:mid, :guard, :operation]).each do |attr| attr_value = case attr when :itype self.itype(:frcp) when :src self.src.is_a?(OmfCommon::Comm::Topic) ? self.src.address : self.src else self.send(attr) end next unless attr_value add_element(attr, attr_value) unless (self.operation != :release && attr == :res_id) end self.properties.each { |k, v| add_property(k, v) unless k == 'certificate'} self.guard.each { |k, v| add_property(k, v, :guard) } if _get_core(:guard) @xml end
each_bound_request_property(&block)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 410 def each_bound_request_property(&block) raise "Can only be used for request messages. Got #{type}." if type != :request properties.each { |k, v| block.call(k, v) unless v.nil? } end
each_property(&block)
click to toggle source
Iterate each property key value pair
# File lib/omf_common/message/xml/message.rb, line 400 def each_property(&block) properties.each { |k, v| block.call(k, v) } end
each_unbound_request_property(&block)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 405 def each_unbound_request_property(&block) raise "Can only be used for request messages. Got #{type}." if type != :request properties.each { |k, v| block.call(k, v) if v.nil? } end
element_by_xpath_with_default_namespace(xpath_without_ns)
click to toggle source
Short cut for grabbing a group of nodes using xpath, but with default namespace
# File lib/omf_common/message/xml/message.rb, line 322 def element_by_xpath_with_default_namespace(xpath_without_ns) xpath_without_ns = xpath_without_ns.to_s if !default_props_ns.empty? && xpath_without_ns !~ /props|guard|ts|src|mid|rtype|res_id|cid|itype/ @xml.xpath(xpath_without_ns.gsub(/(^|\/{1,2})(\w+)/, "\\1#{rtype.to_s}:\\2"), default_props_ns) else @xml.xpath(xpath_without_ns.gsub(/(^|\/{1,2})(\w+)/, '\1xmlns:\2'), :xmlns => OMF_NAMESPACE) end end
Also aliased as: read_element
guard?()
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 388 def guard? @content.guard.empty? end
has_properties?()
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 384 def has_properties? !@content.properties.empty? end
marshall()
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 142 def marshall build_xml if self.class.authenticate? src = @content[:src] issuer = @content[:issuer] src = src.address if src.is_a?(OmfCommon::Comm::Topic) cert = OmfCommon::Auth::CertificateStore.instance.cert_for(issuer) if cert && cert.can_sign? debug "Found cert for '#{src} - #{cert}" signature_node = Niceogiri::XML::Node.new(:sig) canonicalised_xml = self.class.fix_canonicalised_xml(@xml.canonicalize) signature = Base64.encode64(cert.key.sign(OpenSSL::Digest::SHA256.new(canonicalised_xml), canonicalised_xml)).encode('utf-8') signature_node.add_child(signature) @envelope = Niceogiri::XML::Node.new(:env, nil, OMF_NAMESPACE) @envelope.add_child(@xml) @envelope.add_child(signature_node) iss_node = Niceogiri::XML::Node.new(:iss) iss_node.add_child(issuer) @envelope.add_child(iss_node) #unless @certOnTopic[k = [topic, src]] # first time for this src on this topic, so let's send the cert along cert_node = Niceogiri::XML::Node.new(:cert) cert_node.add_child(cert.to_pem_compact) @envelope.add_child(cert_node) #ALWAYS ADD CERT @certOnTopic[k] = Time.now #end ['text/xml', @envelope] else error "Missing cert for #{src}. Auth turned on but could not locate a proper cert." ['text/xml', nil] end else ['text/xml', @xml] end end
print_app_event()
click to toggle source
Pretty print for application event message
# File lib/omf_common/message/xml/message.rb, line 394 def print_app_event "APP_EVENT (#{read_property(:app)}, ##{read_property(:seq)}, #{read_property(:event)}): #{read_property(:msg)}" end
properties()
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 380 def properties @content.properties end
read_content(element_name)
click to toggle source
We just want to know the content of an non-repeatable element
# File lib/omf_common/message/xml/message.rb, line 337 def read_content(element_name) element_content = read_element("#{element_name}").first.content rescue nil unless element_content.nil? element_content.empty? ? nil : element_content else nil end end
read_element(xpath_without_ns)
In case you think method :element_by_xpath_with_default_namespace is too long
Alias for: element_by_xpath_with_default_namespace
reconstruct_data(node, data_binding = nil)
click to toggle source
Reconstruct xml node into Ruby object
@param [Niceogiri::XML::Node] node xml node @return [Object] the content of the property, as string, integer, float, or mash(hash with indifferent access)
# File lib/omf_common/message/xml/message.rb, line 350 def reconstruct_data(node, data_binding = nil) node_type = node.attr('type') case node_type when 'array' node.element_children.map do |child| reconstruct_data(child, data_binding) end when /hash/ mash ||= Hashie::Mash.new node.element_children.each do |child| mash[child.attr('key') || child.element_name] ||= reconstruct_data(child, data_binding) end mash when /boolean/ node.content == "true" else if node.content.empty? nil elsif data_binding && node_type == 'string' ERB.new(node.content).result(data_binding) else node.content.ducktype end end end
to_s()
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 184 def to_s @content end
valid?()
click to toggle source
Validate against relaxng schema
# File lib/omf_common/message/xml/message.rb, line 294 def valid? build_xml validation = RelaxNGSchema.instance.validate(@xml.document) if validation.empty? true else logger.error validation.map(&:message).join("\n") logger.debug @xml.to_s false end end
value_node_set(value, key = nil)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 248 def value_node_set(value, key = nil) case value when Hash [].tap do |array| value.each_pair do |k, v| unless v.nil? k = escape_key(k) n = Niceogiri::XML::Node.new(k, nil, OMF_NAMESPACE) n.write_attr('type', ruby_type_2_prop_type(v.class)) c_node = value_node_set(v, k) if c_node.class == Array c_node.each { |c_n| n.add_child(c_n) } else n.add_child(c_node) end array << n end end end when Array value.map do |v| n = Niceogiri::XML::Node.new('it', nil, OMF_NAMESPACE) n.write_attr('type', ruby_type_2_prop_type(v.class)) c_node = value_node_set(v, 'it') if c_node.class == Array c_node.each { |c_n| n.add_child(c_n) } else n.add_child(c_node) end n end else if key.nil? string_value(value) else key = escape_key(key) n = Niceogiri::XML::Node.new(key, nil, OMF_NAMESPACE) n.add_child(string_value(value)) end end end
Private Instance Methods
_get_core(key)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 447 def _get_core(key) @content[key] end
_get_property(key, ns = nil)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 456 def _get_property(key, ns = nil) # TODO what to do here @content.properties[key] end
_set_core(key, value)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 443 def _set_core(key, value) @content[key] = value end
_set_property(key, value, ns = nil)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 451 def _set_property(key, value, ns = nil) # TODO what to do here @content.properties[key] = value end
escape_key(key)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 477 def escape_key(key) key = key.to_s if key =~ /\W+/ warn "Due to the limitation of XML messages, please only use word character (a-z A-Z 0-9 _) in your property names. Offending characters will be replaced with underscore(_)." key = key.gsub(/\W+/, '_') else key end end
ruby_type_2_prop_type(ruby_class_type)
click to toggle source
# File lib/omf_common/message/xml/message.rb, line 461 def ruby_type_2_prop_type(ruby_class_type) v_type = ruby_class_type.to_s.downcase case v_type when *%w(trueclass falseclass) 'boolean' when *%w(fixnum bignum) 'integer' when /hash|mash/ 'hash' when /symbol/ 'string' else v_type end end
string_value(value)
click to toggle source
Get string of a value object, escape if object is string
# File lib/omf_common/message/xml/message.rb, line 488 def string_value(value) if value.kind_of? String value = CGI::escape_html(value) else value = value.to_s end value end