class Argos::Soap

Constants

ARGOS_NS
NAMESPACES
SOAP_NS
URI
WSDL

Alternative: “ws-argos.clsamerica.com/argosDws/services/DixService

Attributes

client[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

filter[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

log[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

nbDaysFromNow[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

operation[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

password[W]

username [String] password [String]

period[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

platformId[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

programNumber[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

request[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

response[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

username[W]

username [String] password [String]

xml[RW]

client [Savon] (version 3) request [String] Soap:Envelope (XML request body) response [Savon::Response] operation [Savon::Operation] log [Logger] xml [String] (Extracted, inner) XML filter platformId [String] Comma-separated list of platforms programNumber [String] Comma-separated list of programs nbDaysFromNow period

Public Class Methods

new(config={}) click to toggle source

Constructor soap = Argos::Soap.new({username: “argos-system-user”, password: “argos-system-pw”})

# File lib/argos/soap.rb, line 39
def initialize(config={})
  config.each do |k,v|
    case k.to_sym
    when :username
      @username=v
    when :password
      @password=v
    when :wsdl
      @wsdl=v
    when :programNumber
      @programNumber = v
    when :platformId
      @platformId = v
    when :nbDaysFromNow
      @nbDaysFromNow = v.to_i
    when :period
      @period = v
    when :filter
      @filter = v
    else
      #raise ArgumentError, "Unkown config key: #{k}"
    end
  end
end

Public Instance Methods

baseRequest() click to toggle source

Build baseRequest Hash The service requires programNumber or PlatformId, but if you do not provide any, this method will call the service (@see programs) and get the current user’s programs @return [Hash]

# File lib/argos/soap.rb, line 68
def baseRequest
  # if override key is platformId... delete programNumber...
  # if override key is period... delete nbDaysFromNow...
  baseRequest = { username: _username, password: _password }
  
  # First choice (program or platform)
  if @programNumber.nil? and @platformId.nil?
    # Fetch all programs if neither is provided
    baseRequest[:programNumber] = programs.map {|p|p.to_s}.join(",")
  elsif @programNumber.to_s =~ /\d+/ and @platformId.to_s =~ /\d+/
    baseRequest[:platformId] = @platformId # ignores programNumber
  elsif @programNumber.to_s =~ /\d+/ 
    baseRequest[:programNumber] = @programNumber
  elsif @platformId.to_s =~ /\d+/
    baseRequest[:platformId] = @platformId
  end
  
  # 2nd choice (time)
  if @nbDaysFromNow.nil? and @period.nil?
    # Default to 20 days of data (the maximum)
    baseRequest[:nbDaysFromNow] = 20
  elsif @nbDaysFromNow =~ /\d+/ and not @period.nil?
    raise "Cannot provide both nbDaysFromNow and period"
  elsif @nbDaysFromNow.to_s =~ /\d+/ 
    baseRequest[:nbDaysFromNow] = @nbDaysFromNow.to_i
  else
    baseRequest[:period] = @period
  end
  
  #baseRequest = baseRequest.merge({
    # @todo
    #<xs:element minOccurs="0" name="referenceDate" type="tns:referenceDateType"/>
    #<xs:element minOccurs="0" name="locClass" type="xs:string"/>
    #<xs:element minOccurs="0" name="geographicArea" type="xs:string"/>
    #<xs:element minOccurs="0" name="compression" type="xs:int"/>
    #<xs:element minOccurs="0" name="mostRecentPassages" type="xs:boolean"/>
  #})
  baseRequest
  
end
filter?() click to toggle source
# File lib/argos/soap.rb, line 129
def filter?
  not @filter.nil? and filter.respond_to?(:call)
end
getCsv() click to toggle source

@return [String]

# File lib/argos/soap.rb, line 134
def getCsv
  o = _operation(:getCsv)
  o.body = { csvRequest: baseRequest.merge(
    showHeader: true).merge(xmlRequest)
  }
  @response = o.call
  @request = o.build
  
  # Handle faults (before extracting data)
  _envelope.xpath("soap:Body/soap:Fault", namespaces).each do | fault |
    raise fault.to_s
  end
  
  @text = _extract_escaped_xml("csvResponse").call(response)
end
getKml() click to toggle source

@return [Hash]

# File lib/argos/soap.rb, line 151
def getKml
  _call_xml_operation(:getKml, { kmlRequest: baseRequest.merge(xmlRequest)}, _extract_escaped_xml("kmlResponse"))
end
getObsCsv() click to toggle source

@return [Text] choice: programNumber | platformId | wmo* nbMaxObs

# File lib/argos/soap.rb, line 214
def getObsCsv
  o = _operation(:getObsCsv)
  o.body = { observationRequest: baseRequest.merge(xmlRequest)
  }
  @response = o.call
  @request = o.build
  @text = _extract_escaped_xml("observationResponse").call(response)
end
getObsXml() click to toggle source

@return [Hash] choice: programNumber | platformId | wmo* nbMaxObs

# File lib/argos/soap.rb, line 205
def getObsXml
  _call_xml_operation(:getObsXml,
    { observationRequest: baseRequest.merge(xmlRequest)},
    _extract_escaped_xml("observationResponse"))
end
getPlatformList() click to toggle source

@return [Hash] {“data”:{“program”:[{“programNumber”:“9660”,“platform”:[{ .. },{ .. }]}],“@version”:“1.0”}} Each platform Hash (.. above): {“platformId”:“129990”,“lastLocationClass”:“3”,“lastCollectDate”:“2013-10-03T08:32:24.000Z”,“lastLocationDate”:“2013-05-22T04:55:15.000Z”,“lastLatitude”:“47.67801”,“lastLongitude”:“-122.13419”}

# File lib/argos/soap.rb, line 158
def getPlatformList
  platformList = _call_xml_operation(:getPlatformList, { platformListRequest:
    # Cannot use #baseRequest here because that methods calls #programs which also calls #getPlatformList...
    { username: _username, password: _password },
  }, _extract_escaped_xml("platformListResponse"))
  
  # Raise error if no programs
  if platformList["data"]["program"].nil?
    raise platformList.to_json
  end
  # Force Arrays
  if not platformList["data"]["program"].is_a? Array
    platformList["data"]["program"] = [platformList["data"]["program"]]
  end
  
  platformList["data"]["program"].map! {|program|
    if program["platform"].is_a? Hash
      program["platform"] = [program["platform"]]
    end
    program 
  }
  
  platformList
end
getStreamXml() click to toggle source

@return [Hash]

# File lib/argos/soap.rb, line 191
def getStreamXml
  _call_xml_operation(:getStreamXml,
    { streamXmlRequest: baseRequest.merge(xmlRequest)},
    _extract_motm)
end
getXml() click to toggle source

@return [Hash]

# File lib/argos/soap.rb, line 184
def getXml
  _call_xml_operation(:getXml,
    { xmlRequest: baseRequest.merge(xmlRequest)},
    _extract_escaped_xml("xmlResponse"))
end
getXsd() click to toggle source

@return [Hash]

# File lib/argos/soap.rb, line 198
def getXsd  
  _call_xml_operation(:getXsd, { xsdRequest: {} }, _extract_escaped_xml("xsdResponse"))
end
namespaces() click to toggle source
# File lib/argos/soap.rb, line 306
def namespaces
  NAMESPACES
end
operations() click to toggle source

@return [Array] [:getCsv, :getStreamXml, :getKml, :getXml, :getXsd, :getPlatformList, :getObsCsv, :getObsXml]

# File lib/argos/soap.rb, line 302
def operations
  @response = client.operations(:DixService, :DixServicePort)
end
platforms() click to toggle source

Platforms: array of platformId integers @return [Array] of [Integer]

# File lib/argos/soap.rb, line 225
def platforms
  platforms = []

  platformListPrograms = getPlatformList["data"]["program"]

  if @programNumber.to_s =~ /\d+/
    platformListPrograms.select! {|p| p["programNumber"].to_i == @programNumber.to_i }
  end
  
  platformListPrograms.each do |program|
    if program.key?("platform") and not program["platform"].is_a?(Array)
      platforms << program["platform"]["platformId"].to_i
    else
      platforms  += program["platform"].map {|p| p["platformId"].to_i}
    end
  end
  platforms
end
programs() click to toggle source

Programs: Array of programNumber integers @return [Array]

# File lib/argos/soap.rb, line 251
def programs
  platformList = getPlatformList
  if platformList.key?("data") and platformList["data"].key?("program")
    
    platformList_data_program = platformList["data"]["program"].is_a?(Array) ? platformList["data"]["program"] : [platformList["data"]["program"]]
    
    platformList_data_program.map {|p| p["programNumber"].to_i }
    
    
  else
    raise platformList
  end
end
raw() click to toggle source

@return [String]

# File lib/argos/soap.rb, line 266
def raw
  response.raw
end
schema() click to toggle source
# File lib/argos/soap.rb, line 284
def schema
  Nokogiri::XML::Schema(File.read("#{__dir__}/_xsd/argos-data.xsd"))
end
services() click to toggle source

@return [Hash] {“DixService”:{“ports”:{“DixServicePort”:{“type”:“schemas.xmlsoap.org/wsdl/soap12/”,“location”:“http://ws-argos.cls.fr/argosDws/services/DixService”}}}}

# File lib/argos/soap.rb, line 280
def services
  client.services
end
text() click to toggle source

@return [String]

# File lib/argos/soap.rb, line 297
def text
  @text||=""
end
validate(xml) click to toggle source
# File lib/argos/soap.rb, line 288
def validate(xml)
  if xml.is_a? String
    xml = Nokogiri.XML(xml)
  end
  schema.validate(xml)
  
end
xmlRequest() click to toggle source
# File lib/argos/soap.rb, line 310
def xmlRequest
  {
    displayLocation: true,
    displayDiagnostic: true,
    displayMessage: true,
    displayCollect: true,
    displayRawData: true,
    displaySensor: true,
    #argDistrib: "",
    displayImageLocation: true,
    displayHexId: true  
  }
end

Protected Instance Methods

_call_xml_operation(op_sym, body, extract=nil) click to toggle source

Build and call @operation, set @response, @request, and @xml @raise on faults @return [Hash]

# File lib/argos/soap.rb, line 329
def _call_xml_operation(op_sym, body, extract=nil)
  @operation = _operation(op_sym)
  @operation.body = body
  @response = operation.call

  # Check for http errors?

  # Handle faults (before extracting data)
  _envelope.xpath("soap:Body/soap:Fault", namespaces).each do | fault |
    raise Exception, fault.to_s
  end
  
  # Extract data
  if extract.respond_to?(:call)
    @xml = extract.call(response)
  else
    @xml = response.raw
  end
  
  # Handle errors

  ng = Nokogiri.XML(xml)
  ng.xpath("/data/errors/error").each do | error |
    if error.key?("code")
      case error["code"].to_i
      when 4
        raise NodataException
      end
      #<error code="2">max response reached</error>
      #<error code="3">authentification error</error>
      #<error code="9">start date upper than end date</error>
    else
      raise Exception, error
    end
  end
  
  # Validation - only :getXml
  if [:getXml].include? op_sym
    # Validation against getXSD schema does not work: ["Element 'data': No matching global declaration available for the validation root."]
    # See https://github.com/npolar/argos-ruby/commit/219e4b3761e5265f8f9e8b924bcfc23607902428 for the fix
    schema = Nokogiri::XML::Schema(File.read("#{__dir__}/_xsd/argos-data.xsd"))
    v = schema.validate(ng)
    if v.any?
      log.debug "#{v.size} errors: #{v.map{|v|v.to_s}.uniq.to_json}"
    end
  end
  

  # Convert XML to Hash
  nori = Nori.new
  nori.parse(xml)
end
_envelope() click to toggle source

@return [Nokogiri:*]

# File lib/argos/soap.rb, line 413
def _envelope
  ng = Nokogiri.XML(response.raw).xpath("/soap:Envelope", namespaces)
  if not ng.any?
    # Again, this is a shame...
    envstr = '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">'
    extracted = envstr + response.raw.split(envstr)[1].split("</soap:Envelope>")[0] + "</soap:Envelope>"
    ng = Nokogiri.XML(extracted).xpath("/soap:Envelope", namespaces)
  end
  ng
end
_extract_escaped_xml(responseElement) click to toggle source

This is a shame, but who’s to blame when there’s multiple XML prologs (the second is escaped) and even mix of (declared) encodings (UTF-8 in soap envelope, ISO-8859-1 inside) Note: the inner data elements are non-namespaced (see da), so that recreating as proper XML would need to set xmlns=“”

# File lib/argos/soap.rb, line 384
def _extract_escaped_xml(responseElement)
  lambda {|response| CGI.unescapeHTML(response.raw.split("<#{responseElement} xmlns=\"http://service.dataxmldistribution.argos.cls.fr/types\"><return>")[1].split("</return>")[0])}
end
_extract_motm() click to toggle source

@return [String]

# File lib/argos/soap.rb, line 394
def _extract_motm
  lambda {|response|
    # Scan for MOTM signature --uuid:*
    if response.raw =~ (/^(--[\w:-]+)--$/)
      # Get the last message, which is -2 because of the trailing --
      xml = response.raw.split($1)[-2].strip

      # Get rid of HTTP headers
      if xml =~ /\r\n\r\n[<]/
        xml = xml.split(/\r\n\r\n/)[-1]
      end
      
    else
      raise "Cannot parse MOTM"
    end
    }
end
_operation(operation_name) click to toggle source

@return [Savon::Operation]

# File lib/argos/soap.rb, line 425
def _operation(operation_name)
  client.operation(:DixService, :DixServicePort, operation_name)
end
_password() click to toggle source

@return [String]

# File lib/argos/soap.rb, line 435
def _password
  @password||=ENV["ARGOS_SOAP_PASSWORD"]
end
_username() click to toggle source

@return [String]

# File lib/argos/soap.rb, line 430
def _username
  @username||=ENV["ARGOS_SOAP_USERNAME"]
end