hoopla-savon 0.7.6

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 (52) hide show
  1. data/CHANGELOG +156 -0
  2. data/README.rdoc +64 -0
  3. data/Rakefile +51 -0
  4. data/lib/savon/client.rb +112 -0
  5. data/lib/savon/core_ext/array.rb +31 -0
  6. data/lib/savon/core_ext/datetime.rb +8 -0
  7. data/lib/savon/core_ext/hash.rb +102 -0
  8. data/lib/savon/core_ext/net_http.rb +19 -0
  9. data/lib/savon/core_ext/object.rb +19 -0
  10. data/lib/savon/core_ext/string.rb +66 -0
  11. data/lib/savon/core_ext/symbol.rb +8 -0
  12. data/lib/savon/core_ext/uri.rb +10 -0
  13. data/lib/savon/core_ext.rb +8 -0
  14. data/lib/savon/logger.rb +56 -0
  15. data/lib/savon/request.rb +135 -0
  16. data/lib/savon/response.rb +155 -0
  17. data/lib/savon/soap.rb +302 -0
  18. data/lib/savon/wsdl.rb +142 -0
  19. data/lib/savon/wsdl_stream.rb +85 -0
  20. data/lib/savon/wsse.rb +163 -0
  21. data/lib/savon.rb +32 -0
  22. data/spec/basic_spec_helper.rb +11 -0
  23. data/spec/endpoint_helper.rb +23 -0
  24. data/spec/fixtures/response/response_fixture.rb +36 -0
  25. data/spec/fixtures/response/xml/authentication.xml +14 -0
  26. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  27. data/spec/fixtures/response/xml/soap_fault.xml +8 -0
  28. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  29. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  30. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  31. data/spec/fixtures/wsdl/xml/geotrust.xml +156 -0
  32. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  33. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  34. data/spec/http_stubs.rb +27 -0
  35. data/spec/integration/http_basic_auth_spec.rb +16 -0
  36. data/spec/integration/server.rb +51 -0
  37. data/spec/savon/client_spec.rb +81 -0
  38. data/spec/savon/core_ext/array_spec.rb +19 -0
  39. data/spec/savon/core_ext/datetime_spec.rb +12 -0
  40. data/spec/savon/core_ext/hash_spec.rb +178 -0
  41. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  42. data/spec/savon/core_ext/object_spec.rb +40 -0
  43. data/spec/savon/core_ext/string_spec.rb +87 -0
  44. data/spec/savon/core_ext/symbol_spec.rb +11 -0
  45. data/spec/savon/core_ext/uri_spec.rb +19 -0
  46. data/spec/savon/request_spec.rb +93 -0
  47. data/spec/savon/response_spec.rb +137 -0
  48. data/spec/savon/soap_spec.rb +204 -0
  49. data/spec/savon/wsdl_spec.rb +110 -0
  50. data/spec/savon/wsse_spec.rb +132 -0
  51. data/spec/spec_helper.rb +11 -0
  52. metadata +206 -0
@@ -0,0 +1,56 @@
1
+ module Savon
2
+
3
+ # = Savon::Logger
4
+ #
5
+ # Savon::Logger can be mixed into classes to provide logging behavior.
6
+ #
7
+ # By default, the Logger mixin uses {Ruby's Logger}[http://ruby-doc.org/stdlib/libdoc/logger/rdoc/]
8
+ # from the standard library, a log level of :debug and is pointing to STDOUT.
9
+ module Logger
10
+
11
+ module ClassMethods
12
+
13
+ # Sets whether to log.
14
+ def log=(log)
15
+ @log = log
16
+ end
17
+
18
+ # Returns whether to log. Defaults to +true+.
19
+ def log?
20
+ @log != false
21
+ end
22
+
23
+ # Sets the logger.
24
+ def logger=(logger)
25
+ @logger = logger
26
+ end
27
+
28
+ # Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
29
+ def logger
30
+ @logger ||= ::Logger.new STDOUT
31
+ end
32
+
33
+ # Sets the log level.
34
+ def log_level=(log_level)
35
+ @log_level = log_level
36
+ end
37
+
38
+ # Returns the log level. Defaults to +debug+.
39
+ def log_level
40
+ @log_level ||= :debug
41
+ end
42
+
43
+ end
44
+
45
+ # Extends the class including this module with its ClassMethods.
46
+ def self.included(base)
47
+ base.extend ClassMethods
48
+ end
49
+
50
+ # Logs a given +message+.
51
+ def log(message)
52
+ self.class.logger.send self.class.log_level, message if self.class.log?
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,135 @@
1
+ module Savon
2
+
3
+ # = Savon::Request
4
+ #
5
+ # Savon::Request handles both WSDL and SOAP requests.
6
+ #
7
+ # == The Net::HTTP object
8
+ #
9
+ # You can access the Net::HTTP object used for both WSDL and SOAP requests via:
10
+ #
11
+ # client.request.http
12
+ #
13
+ # Here's an example of how to set open and read timeouts on the Net::HTTP object.
14
+ #
15
+ # client.request.http.open_timeout = 30
16
+ # client.request.http.read_timeout = 30
17
+ #
18
+ # Please refer to the {Net::HTTP documentation}[http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/]
19
+ # for more information.
20
+ #
21
+ # == HTTP basic authentication
22
+ #
23
+ # Setting credentials for HTTP basic authentication:
24
+ #
25
+ # client.request.basic_auth "username", "password"
26
+ #
27
+ # == SSL client authentication
28
+ #
29
+ # You can use the methods provided by Net::HTTP to set SSL client authentication or use a shortcut:
30
+ #
31
+ # client.request.http.ssl_client_auth(
32
+ # :cert => OpenSSL::X509::Certificate.new(File.read("client_cert.pem")),
33
+ # :key => OpenSSL::PKey::RSA.new(File.read("client_key.pem"), "password if one exists"),
34
+ # :ca_file => "cacert.pem",
35
+ # :verify_mode => OpenSSL::SSL::VERIFY_PEER
36
+ # )
37
+ #
38
+ # == HTTP headers
39
+ #
40
+ # There's an accessor for the Hash of HTTP headers sent with any SOAP call:
41
+ #
42
+ # client.request.headers["custom"] = "header"
43
+ class Request
44
+ include Logger
45
+
46
+ # Content-Types by SOAP version.
47
+ ContentType = { 1 => "text/xml;charset=UTF-8", 2 => "application/soap+xml;charset=UTF-8" }
48
+
49
+ # Expects a SOAP +endpoint+ String. Also accepts an optional Hash
50
+ # of +options+ for specifying a proxy server.
51
+ def initialize(endpoint, options = {})
52
+ @endpoint = URI endpoint
53
+ @proxy = options[:proxy] ? URI(options[:proxy]) : URI("")
54
+ end
55
+
56
+ # Returns the endpoint URI.
57
+ attr_reader :endpoint
58
+
59
+ # Returns the proxy URI.
60
+ attr_reader :proxy
61
+
62
+ # Returns the HTTP headers for a SOAP request.
63
+ def headers
64
+ @headers ||= {}
65
+ end
66
+
67
+ # Sets the HTTP headers for a SOAP request.
68
+ def headers=(headers)
69
+ @headers = headers if headers.kind_of? Hash
70
+ end
71
+
72
+ # Sets the +username+ and +password+ for HTTP basic authentication.
73
+ def basic_auth(username, password)
74
+ @basic_auth = [username, password]
75
+ end
76
+
77
+ # Retrieves WSDL document and returns the Net::HTTP response.
78
+ def wsdl
79
+ log "Retrieving WSDL from: #{@endpoint}"
80
+ Kernel.open(@endpoint.to_s)
81
+ end
82
+
83
+ # Executes a SOAP request using a given Savon::SOAP instance and returns the Net::HTTP response.
84
+ def soap(soap)
85
+ @soap = soap
86
+ http.endpoint @soap.endpoint.host, @soap.endpoint.port
87
+ http.use_ssl = @soap.endpoint.ssl?
88
+
89
+ log_request
90
+ @response = http.start do |h|
91
+ h.request request(:soap) { |request| request.body = @soap.to_xml }
92
+ end
93
+ log_response
94
+ @response
95
+ end
96
+
97
+ # Returns the Net::HTTP object.
98
+ def http
99
+ @http ||= Net::HTTP::Proxy(@proxy.host, @proxy.port).new @endpoint.host, @endpoint.port
100
+ end
101
+
102
+ private
103
+
104
+ # Logs the SOAP request.
105
+ def log_request
106
+ log "SOAP request: #{@soap.endpoint}"
107
+ log soap_headers.merge(headers).map { |key, value| "#{key}: #{value}" }.join(", ")
108
+ log @soap.to_xml
109
+ end
110
+
111
+ # Logs the SOAP response.
112
+ def log_response
113
+ log "SOAP response (status #{@response.code}):"
114
+ log @response.body
115
+ end
116
+
117
+ # Returns a Net::HTTP request for a given +type+. Yields the request to an optional block.
118
+ def request(type)
119
+ request = case type
120
+ when :wsdl then Net::HTTP::Get.new @endpoint.request_uri
121
+ when :soap then Net::HTTP::Post.new @soap.endpoint.request_uri, soap_headers.merge(headers)
122
+ end
123
+
124
+ request.basic_auth(*@basic_auth) if @basic_auth
125
+ yield request if block_given?
126
+ request
127
+ end
128
+
129
+ # Returns a Hash containing the SOAP headers for an HTTP request.
130
+ def soap_headers
131
+ { "Content-Type" => ContentType[@soap.version], "SOAPAction" => @soap.action }
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,155 @@
1
+ module Savon
2
+
3
+ # = Savon::Response
4
+ #
5
+ # Savon::Response represents both HTTP and SOAP response.
6
+ #
7
+ # == SOAP fault
8
+ #
9
+ # Assuming the default behavior of raising errors is disabled, you can ask the response object
10
+ # if there was a SOAP fault or an HTTP error and get the SOAP fault or HTTP error message.
11
+ #
12
+ # response.soap_fault?
13
+ # # => true
14
+ #
15
+ # response.soap_fault
16
+ # # => "(soap:Server) Fault occurred while processing."
17
+ #
18
+ # response.http_error?
19
+ # # => true
20
+ #
21
+ # response.http_error
22
+ # # => "Not found (404)"
23
+ #
24
+ # == Response as XML
25
+ #
26
+ # To get the raw SOAP response XML, you can call to_xml or to_s on the response object.
27
+ #
28
+ # response.to_xml
29
+ # => "<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
30
+ # => "..."
31
+ # => "</soap:Envelope>"
32
+ #
33
+ # == Response as a Hash
34
+ #
35
+ # You can also let Savon translate the SOAP response body to a Hash.
36
+ #
37
+ # response.to_hash
38
+ # => { :findUserByIdResponse => {
39
+ # => :id => "123",
40
+ # => :username => "eve"
41
+ # => :active => true
42
+ # => }
43
+ #
44
+ # When translating the SOAP response to a Hash, some XML tags and values are converted to more
45
+ # convenient Ruby objects. Translation is done through John Nunemaker's {Crack}[http://github.com/jnunemaker/crack]
46
+ # library along with some custom mapping.
47
+ #
48
+ # * XML tags (Hash keys) are converted to snake_case Symbols and namespaces are stripped off
49
+ # * SOAP xs:nil values are converted to nil objects
50
+ # * XML values specified in xs:DateTime format are converted to DateTime objects
51
+ # * XML values of "true" and "false" are converted to TrueClass and FalseClass
52
+ #
53
+ # == Net::HTTP response
54
+ #
55
+ # If for some reason you need to access the Net::HTTP response object ... you can.
56
+ #
57
+ # bc. response.http
58
+ # => #<Net::HTTPOK:0x7f749a1aa4a8>
59
+ class Response
60
+
61
+ # The maximum HTTP response code considered to be OK.
62
+ MaxNonErrorResponseCode = 299
63
+
64
+ # The global setting of whether to raise errors.
65
+ @@raise_errors = true
66
+
67
+ # Sets the global setting of whether to raise errors.
68
+ def self.raise_errors=(raise_errors)
69
+ @@raise_errors = raise_errors
70
+ end
71
+
72
+ # Returns the global setting of whether to raise errors.
73
+ def self.raise_errors?
74
+ @@raise_errors
75
+ end
76
+
77
+ # Expects a Net::HTTPResponse and handles errors.
78
+ def initialize(http)
79
+ @http = http
80
+
81
+ handle_soap_fault
82
+ handle_http_error
83
+ end
84
+
85
+ # Returns whether there was a SOAP fault.
86
+ def soap_fault?
87
+ !@soap_fault.blank?
88
+ end
89
+
90
+ # Returns the SOAP fault message.
91
+ attr_reader :soap_fault
92
+
93
+ # Returns whether there was an HTTP error.
94
+ def http_error?
95
+ !@http_error.blank?
96
+ end
97
+
98
+ # Returns the HTTP error message.
99
+ attr_reader :http_error
100
+
101
+ # Returns the SOAP response body as a Hash.
102
+ def to_hash
103
+ @body ||= (Crack::XML.parse(@http.body) rescue {}).find_soap_body
104
+ end
105
+
106
+ # Returns the SOAP response XML.
107
+ def to_xml
108
+ @http.body
109
+ end
110
+
111
+ # Returns the HTTP response object.
112
+ attr_reader :http
113
+
114
+ alias :to_s :to_xml
115
+
116
+ private
117
+
118
+ # Handles SOAP faults. Raises a Savon::SOAPFault unless the default behavior of raising errors
119
+ # was turned off.
120
+ def handle_soap_fault
121
+ if soap_fault_message
122
+ @soap_fault = soap_fault_message
123
+ raise Savon::SOAPFault, @soap_fault if self.class.raise_errors?
124
+ end
125
+ end
126
+
127
+ # Returns a SOAP fault message in case a SOAP fault was found.
128
+ def soap_fault_message
129
+ @soap_fault_message ||= soap_fault_message_by_version to_hash[:fault]
130
+ end
131
+
132
+ # Expects a Hash that might contain information about a SOAP fault. Returns the SOAP fault
133
+ # message in case one was found.
134
+ def soap_fault_message_by_version(soap_fault)
135
+ return unless soap_fault
136
+
137
+ if soap_fault.keys.include? :faultcode
138
+ "(#{soap_fault[:faultcode]}) #{soap_fault[:faultstring]}"
139
+ elsif soap_fault.keys.include? :code
140
+ "(#{soap_fault[:code][:value]}) #{soap_fault[:reason][:text]}"
141
+ end
142
+ end
143
+
144
+ # Handles HTTP errors. Raises a Savon::HTTPError unless the default behavior of raising errors
145
+ # was turned off.
146
+ def handle_http_error
147
+ if @http.code.to_i > MaxNonErrorResponseCode
148
+ @http_error = "#{@http.message} (#{@http.code})"
149
+ @http_error << ": #{@http.body}" unless @http.body.empty?
150
+ raise Savon::HTTPError, http_error if self.class.raise_errors?
151
+ end
152
+ end
153
+
154
+ end
155
+ end
data/lib/savon/soap.rb ADDED
@@ -0,0 +1,302 @@
1
+ module Savon
2
+
3
+ # = Savon::SOAP
4
+ #
5
+ # Savon::SOAP represents the SOAP request. Pass a block to your SOAP call and the SOAP object is
6
+ # passed to it as the first argument. The object allows setting the SOAP version, header, body
7
+ # and namespaces per request.
8
+ #
9
+ # == Body
10
+ #
11
+ # The body method lets you specify parameters to be received by the SOAP action.
12
+ #
13
+ # You can either pass in a hash (which will be translated to XML via Hash.to_soap_xml):
14
+ #
15
+ # response = client.get_user_by_id do |soap|
16
+ # soap.body = { :id => 123 }
17
+ # end
18
+ #
19
+ # Or a string containing the raw XML:
20
+ #
21
+ # response = client.get_user_by_id do |soap|
22
+ # soap.body = "<id>123</id>"
23
+ # end
24
+ #
25
+ # Request output:
26
+ #
27
+ # <env:Envelope
28
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
29
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
30
+ # <env:Body>
31
+ # <wsdl:getUserById><id>123</id></wsdl:getUserById>
32
+ # </env:Body>
33
+ # </env:Envelope>
34
+ #
35
+ # Please look at the documentation of Hash.to_soap_xml for some more information.
36
+ #
37
+ # == Version
38
+ #
39
+ # Savon defaults to SOAP 1.1. In case your service uses SOAP 1.2, you can use the version method
40
+ # to change the default per request.
41
+ #
42
+ # response = client.get_all_users do |soap|
43
+ # soap.version = 2
44
+ # end
45
+ #
46
+ # You can also change the default to SOAP 1.2 for all request:
47
+ #
48
+ # Savon::SOAP.version = 2
49
+ #
50
+ # == Header
51
+ #
52
+ # If you need to add custom XML into the SOAP header, you can use the header method.
53
+ #
54
+ # The value is expected to be a hash (which will be translated to XML via Hash.to_soap_xml):
55
+ #
56
+ # response = client.get_all_users do |soap|
57
+ # soap.header["specialApiKey"] = "secret"
58
+ # end
59
+ #
60
+ # Or a string containing the raw XML:
61
+ #
62
+ # response = client.get_all_users do |soap|
63
+ # soap.header = "<specialApiKey>secret</specialApiKey>"
64
+ # end
65
+ #
66
+ # Request output:
67
+ #
68
+ # <env:Envelope
69
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
70
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
71
+ # <env:Header>
72
+ # <specialApiKey>secret</specialApiKey>
73
+ # </env:Header>
74
+ # <env:Body>
75
+ # <wsdl:getAllUsers></wsdl:getAllUsers>
76
+ # </env:Body>
77
+ # </env:Envelope>
78
+ #
79
+ # == Namespaces
80
+ #
81
+ # The namespaces method contains a hash of attributes for the SOAP envelope. You can overwrite it
82
+ # or add additional attributes.
83
+ #
84
+ # response = client.get_all_users do |soap|
85
+ # soap.namespaces["xmlns:domains"] = "http://domains.example.com"
86
+ # end
87
+ #
88
+ # Request output:
89
+ #
90
+ # <env:Envelope
91
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
92
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
93
+ # xmlns:domains="http://domains.example.com">
94
+ # <env:Body>
95
+ # <wsdl:getAllUsers></wsdl:getAllUsers>
96
+ # </env:Body>
97
+ # </env:Envelope>
98
+ #
99
+ # == Input
100
+ #
101
+ # You can change the name of the SOAP input tag in case you need to.
102
+ #
103
+ # response = client.get_all_users do |soap|
104
+ # soap.input = "GetAllUsersRequest"
105
+ # end
106
+ #
107
+ # Request output:
108
+ #
109
+ # <env:Envelope
110
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
111
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
112
+ # <env:Body>
113
+ # <wsdl:GetAllUsersRequest></wsdl:GetAllUsersRequest>
114
+ # </env:Body>
115
+ # </env:Envelope>
116
+ class SOAP
117
+
118
+ # Supported SOAP versions.
119
+ Versions = [1, 2]
120
+
121
+ # SOAP namespaces by SOAP version.
122
+ Namespace = {
123
+ 1 => "http://schemas.xmlsoap.org/soap/envelope/",
124
+ 2 => "http://www.w3.org/2003/05/soap-envelope"
125
+ }
126
+
127
+ # Content-Types by SOAP version.
128
+ ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
129
+
130
+ # SOAP xs:dateTime format.
131
+ DateTimeFormat = "%Y-%m-%dT%H:%M:%SZ"
132
+
133
+ # SOAP xs:dateTime Regexp.
134
+ DateTimeRegexp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
135
+
136
+ # The global SOAP version.
137
+ @@version = 1
138
+
139
+ # Returns the global SOAP version.
140
+ def self.version
141
+ @@version
142
+ end
143
+
144
+ # Sets the global SOAP version.
145
+ def self.version=(version)
146
+ @@version = version if Versions.include? version
147
+ end
148
+
149
+ # Sets the global SOAP header. Expected to be a Hash that can be translated to XML via
150
+ # Hash.to_soap_xml or any other Object responding to to_s.
151
+ def self.header=(header)
152
+ @@header = header
153
+ end
154
+
155
+ # Returns the global SOAP header. Defaults to an empty Hash.
156
+ def self.header
157
+ @@header ||= {}
158
+ end
159
+
160
+ # Sets the global namespaces. Expected to be a Hash containing the namespaces (keys) and the
161
+ # corresponding URI's (values).
162
+ def self.namespaces=(namespaces)
163
+ @@namespaces = namespaces if namespaces.kind_of? Hash
164
+ end
165
+
166
+ # Returns the global namespaces. A Hash containing the namespaces (keys) and the corresponding
167
+ # URI's (values).
168
+ def self.namespaces
169
+ @@namespaces ||= {}
170
+ end
171
+
172
+ # Initialzes the SOAP object. Expects a SOAP +operation+ Hash along with an +endpoint+.
173
+ def initialize(action, input, endpoint)
174
+ @action, @input = action, input
175
+ @endpoint = endpoint.kind_of?(URI) ? endpoint : URI(endpoint)
176
+ @builder = Builder::XmlMarkup.new
177
+ end
178
+
179
+ # Sets the WSSE options.
180
+ attr_writer :wsse
181
+
182
+ # Sets the SOAP action.
183
+ attr_writer :action
184
+
185
+ # Returns the SOAP action.
186
+ def action
187
+ @action ||= ""
188
+ end
189
+
190
+ # Sets the SOAP input.
191
+ attr_writer :input
192
+
193
+ # Returns the SOAP input.
194
+ def input
195
+ @input ||= ""
196
+ end
197
+
198
+ # Accessor for the SOAP endpoint.
199
+ attr_accessor :endpoint
200
+
201
+ # Sets the SOAP header. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
202
+ # or any other Object responding to to_s.
203
+ attr_writer :header
204
+
205
+ # Returns the SOAP header. Defaults to an empty Hash.
206
+ def header
207
+ @header ||= {}
208
+ end
209
+
210
+ # Accessor for the SOAP body. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
211
+ # or any other Object responding to to_s.
212
+ attr_accessor :body
213
+
214
+ # Accessor for overwriting the default SOAP request. Let's you specify completely custom XML.
215
+ attr_accessor :xml
216
+
217
+ # Sets the namespaces. Expected to be a Hash containing the namespaces (keys) and the
218
+ # corresponding URI's (values).
219
+ attr_writer :namespaces
220
+
221
+ # Returns the namespaces. A Hash containing the namespaces (keys) and the corresponding URI's
222
+ # (values). Defaults to a Hash containing an +xmlns:env+ key and the namespace for the current
223
+ # SOAP version.
224
+ def namespaces
225
+ @namespaces ||= { "xmlns:env" => Namespace[version] }
226
+ end
227
+
228
+ # Convenience method for setting the +xmlns:wsdl+ namespace.
229
+ def namespace=(namespace)
230
+ namespaces["xmlns:wsdl"] = namespace
231
+ end
232
+
233
+ # Sets the SOAP version.
234
+ def version=(version)
235
+ @version = version if Versions.include? version
236
+ end
237
+
238
+ # Returns the SOAP version. Defaults to the global default.
239
+ def version
240
+ @version ||= self.class.version
241
+ end
242
+
243
+ # Returns the SOAP envelope XML.
244
+ def to_xml
245
+ unless @xml
246
+ @builder.instruct!
247
+ @xml = @builder.env :Envelope, merged_namespaces do |xml|
248
+ xml.env(:Header) { xml << merged_header } unless merged_header.empty?
249
+ xml_body xml
250
+ end
251
+ end
252
+ @xml
253
+ end
254
+
255
+ private
256
+
257
+ # Returns a String containing the global and per request header.
258
+ def merged_header
259
+ if self.class.header.kind_of?(Hash) && header.kind_of?(Hash)
260
+ merged_header = self.class.header.merge(header).to_soap_xml
261
+ else
262
+ global_header = self.class.header.to_soap_xml rescue self.class.header.to_s
263
+ request_header = header.to_soap_xml rescue header.to_s
264
+ merged_header = global_header + request_header
265
+ end
266
+ merged_header + wsse_header
267
+ end
268
+
269
+ # Returns the WSSE header or an empty String in case WSSE was not set.
270
+ def wsse_header
271
+ @wsse.respond_to?(:header) ? @wsse.header : ""
272
+ end
273
+
274
+ # Adds a SOAP XML body to a given +xml+ Object.
275
+ def xml_body(xml)
276
+ xml.env(:Body) do
277
+ xml.tag!(:wsdl, *input_array) { xml << (@body.to_soap_xml rescue @body.to_s) }
278
+ end
279
+ end
280
+
281
+ # Returns a Hash containing the global and per request namespaces.
282
+ def merged_namespaces
283
+ self.class.namespaces.merge namespaces
284
+ end
285
+
286
+ # Returns an Array of SOAP input names to append to the wsdl namespace. Defaults to use the
287
+ # name of the SOAP action. May return an empty Array in case the specified SOAP input seems
288
+ # to be invalid.
289
+ def input_array
290
+ if input.kind_of?(Array) && !input.blank?
291
+ [input[0].to_sym, input[1]]
292
+ elsif !input.blank?
293
+ [input.to_sym]
294
+ elsif !action.blank?
295
+ [action.to_sym]
296
+ else
297
+ []
298
+ end
299
+ end
300
+
301
+ end
302
+ end