module Savon
# = Savon::SOAP
#
# Savon::SOAP represents the SOAP request. Pass a block to your SOAP call and the SOAP object is
# passed to it as the first argument. The object allows setting the SOAP version, header, body
# and namespaces per request.
#
# == Body
#
# The body method lets you specify parameters to be received by the SOAP action.
#
# You can either pass in a hash (which will be translated to XML via Hash.to_soap_xml):
#
# response = client.get_user_by_id do |soap|
# soap.body = { :id => 123 }
# end
#
# Or a string containing the raw XML:
#
# response = client.get_user_by_id do |soap|
# soap.body = "123"
# end
#
# Request output:
#
#
#
# 123
#
#
#
# Please look at the documentation of Hash.to_soap_xml for some more information.
#
# == Version
#
# Savon defaults to SOAP 1.1. In case your service uses SOAP 1.2, you can use the version method
# to change the default per request.
#
# response = client.get_all_users do |soap|
# soap.version = 2
# end
#
# You can also change the default to SOAP 1.2 for all request:
#
# Savon::SOAP.version = 2
#
# == Header
#
# If you need to add custom XML into the SOAP header, you can use the header method.
#
# The value is expected to be a hash (which will be translated to XML via Hash.to_soap_xml):
#
# response = client.get_all_users do |soap|
# soap.header["specialApiKey"] = "secret"
# end
#
# Or a string containing the raw XML:
#
# response = client.get_all_users do |soap|
# soap.header = "secret"
# end
#
# Request output:
#
#
#
# secret
#
#
#
#
#
#
# == Namespaces
#
# The namespaces method contains a hash of attributes for the SOAP envelope. You can overwrite it
# or add additional attributes.
#
# response = client.get_all_users do |soap|
# soap.namespaces["xmlns:domains"] = "http://domains.example.com"
# end
#
# Request output:
#
#
#
#
#
#
#
# == Input
#
# You can change the name of the SOAP input tag in case you need to.
#
# response = client.get_all_users do |soap|
# soap.input = "GetAllUsersRequest"
# end
#
# Request output:
#
#
#
#
#
#
class SOAP
# Supported SOAP versions.
Versions = [1, 2]
# SOAP namespaces by SOAP version.
Namespace = {
1 => "http://schemas.xmlsoap.org/soap/envelope/",
2 => "http://www.w3.org/2003/05/soap-envelope"
}
# Content-Types by SOAP version.
ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
# SOAP xs:dateTime format.
DateTimeFormat = "%Y-%m-%dT%H:%M:%S%Z"
# SOAP xs:dateTime Regexp.
DateTimeRegexp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
# The global SOAP version.
@@version = 1
# Returns the global SOAP version.
def self.version
@@version
end
# Sets the global SOAP version.
def self.version=(version)
@@version = version if Versions.include? version
end
# Sets the global SOAP header. Expected to be a Hash that can be translated to XML via
# Hash.to_soap_xml or any other Object responding to to_s.
def self.header=(header)
@@header = header
end
# Returns the global SOAP header. Defaults to an empty Hash.
def self.header
@@header ||= {}
end
# Sets the global namespaces. Expected to be a Hash containing the namespaces (keys) and the
# corresponding URI's (values).
def self.namespaces=(namespaces)
@@namespaces = namespaces if namespaces.kind_of? Hash
end
# Returns the global namespaces. A Hash containing the namespaces (keys) and the corresponding
# URI's (values).
def self.namespaces
@@namespaces ||= {}
end
# Initialzes the SOAP object. Expects a SOAP +operation+ Hash along with an +endpoint+.
def initialize(action, input, endpoint)
@action, @input = action, input
@endpoint = endpoint.kind_of?(URI) ? endpoint : URI(endpoint)
@builder = Builder::XmlMarkup.new
end
# Sets the WSSE options.
attr_writer :wsse
# Sets the SOAP action.
attr_writer :action
# Returns the SOAP action.
def action
@action ||= ""
end
# Sets the SOAP input.
attr_writer :input
# Returns the SOAP input.
def input
@input ||= ""
end
# Accessor for the SOAP endpoint.
attr_accessor :endpoint
# Sets the SOAP header. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
# or any other Object responding to to_s.
attr_writer :header
# Returns the SOAP header. Defaults to an empty Hash.
def header
@header ||= {}
end
# Accessor for the SOAP body. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
# or any other Object responding to to_s.
attr_accessor :body
# Accessor for overwriting the default SOAP request. Let's you specify completely custom XML.
attr_accessor :xml
# Sets the namespaces. Expected to be a Hash containing the namespaces (keys) and the
# corresponding URI's (values).
attr_writer :namespaces
# Returns the namespaces. A Hash containing the namespaces (keys) and the corresponding URI's
# (values). Defaults to a Hash containing an +xmlns:env+ key and the namespace for the current
# SOAP version.
def namespaces
@namespaces ||= { "xmlns:env" => Namespace[version] }
end
# Convenience method for setting the +xmlns:wsdl+ namespace.
def namespace=(namespace)
namespaces["xmlns:wsdl"] = namespace
end
# Sets the SOAP version.
def version=(version)
@version = version if Versions.include? version
end
# Returns the SOAP version. Defaults to the global default.
def version
@version ||= self.class.version
end
# Returns the SOAP envelope XML.
def to_xml
unless @xml
@builder.instruct!
@xml = @builder.env :Envelope, merged_namespaces do |xml|
xml.env(:Header) { xml << merged_header } unless merged_header.empty?
xml_body xml
end
end
@xml
end
private
# Returns a String containing the global and per request header.
def merged_header
if self.class.header.kind_of?(Hash) && header.kind_of?(Hash)
merged_header = self.class.header.merge(header).to_soap_xml
else
global_header = self.class.header.to_soap_xml rescue self.class.header.to_s
request_header = header.to_soap_xml rescue header.to_s
merged_header = global_header + request_header
end
merged_header + wsse_header
end
# Returns the WSSE header or an empty String in case WSSE was not set.
def wsse_header
@wsse.respond_to?(:header) ? @wsse.header : ""
end
# Adds a SOAP XML body to a given +xml+ Object.
def xml_body(xml)
xml.env(:Body) do
xml.tag!(:wsdl, *input_array) { xml << (@body.to_soap_xml rescue @body.to_s) }
end
end
# Returns a Hash containing the global and per request namespaces.
def merged_namespaces
self.class.namespaces.merge namespaces
end
# Returns an Array of SOAP input names to append to the wsdl namespace. Defaults to use the
# name of the SOAP action. May return an empty Array in case the specified SOAP input seems
# to be invalid.
def input_array
if input.kind_of?(Array) && !input.blank?
[input[0].to_sym, input[1]]
elsif !input.blank?
[input.to_sym]
elsif !action.blank?
[action.to_sym]
else
[]
end
end
end
end