actionwebservice 0.5.0

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 (54) hide show
  1. data/ChangeLog +47 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +238 -0
  4. data/Rakefile +144 -0
  5. data/TODO +13 -0
  6. data/examples/googlesearch/README +143 -0
  7. data/examples/googlesearch/autoloading/google_search_api.rb +50 -0
  8. data/examples/googlesearch/autoloading/google_search_controller.rb +57 -0
  9. data/examples/googlesearch/delegated/google_search_service.rb +108 -0
  10. data/examples/googlesearch/delegated/search_controller.rb +7 -0
  11. data/examples/googlesearch/direct/google_search_api.rb +50 -0
  12. data/examples/googlesearch/direct/search_controller.rb +58 -0
  13. data/examples/metaWeblog/README +16 -0
  14. data/examples/metaWeblog/blog_controller.rb +127 -0
  15. data/lib/action_web_service.rb +60 -0
  16. data/lib/action_web_service/api.rb +2 -0
  17. data/lib/action_web_service/api/abstract.rb +192 -0
  18. data/lib/action_web_service/api/action_controller.rb +92 -0
  19. data/lib/action_web_service/base.rb +41 -0
  20. data/lib/action_web_service/client.rb +3 -0
  21. data/lib/action_web_service/client/base.rb +39 -0
  22. data/lib/action_web_service/client/soap_client.rb +88 -0
  23. data/lib/action_web_service/client/xmlrpc_client.rb +77 -0
  24. data/lib/action_web_service/container.rb +85 -0
  25. data/lib/action_web_service/dispatcher.rb +2 -0
  26. data/lib/action_web_service/dispatcher/abstract.rb +150 -0
  27. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +299 -0
  28. data/lib/action_web_service/invocation.rb +205 -0
  29. data/lib/action_web_service/protocol.rb +4 -0
  30. data/lib/action_web_service/protocol/abstract.rb +128 -0
  31. data/lib/action_web_service/protocol/registry.rb +55 -0
  32. data/lib/action_web_service/protocol/soap_protocol.rb +484 -0
  33. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +168 -0
  34. data/lib/action_web_service/struct.rb +55 -0
  35. data/lib/action_web_service/support/class_inheritable_options.rb +26 -0
  36. data/lib/action_web_service/support/signature.rb +100 -0
  37. data/setup.rb +1360 -0
  38. data/test/abstract_client.rb +131 -0
  39. data/test/abstract_soap.rb +58 -0
  40. data/test/abstract_unit.rb +9 -0
  41. data/test/api_test.rb +52 -0
  42. data/test/base_test.rb +42 -0
  43. data/test/client_soap_test.rb +93 -0
  44. data/test/client_xmlrpc_test.rb +92 -0
  45. data/test/container_test.rb +53 -0
  46. data/test/dispatcher_action_controller_test.rb +186 -0
  47. data/test/gencov +3 -0
  48. data/test/invocation_test.rb +149 -0
  49. data/test/protocol_registry_test.rb +53 -0
  50. data/test/protocol_soap_test.rb +252 -0
  51. data/test/protocol_xmlrpc_test.rb +147 -0
  52. data/test/run +5 -0
  53. data/test/struct_test.rb +40 -0
  54. metadata +131 -0
@@ -0,0 +1,4 @@
1
+ require 'action_web_service/protocol/abstract'
2
+ require 'action_web_service/protocol/registry'
3
+ require 'action_web_service/protocol/soap_protocol'
4
+ require 'action_web_service/protocol/xmlrpc_protocol'
@@ -0,0 +1,128 @@
1
+ module ActionWebService # :nodoc:
2
+ module Protocol # :nodoc:
3
+ CheckedMessage = :checked
4
+ UncheckedMessage = :unchecked
5
+
6
+ class ProtocolError < ActionWebService::ActionWebServiceError # :nodoc:
7
+ end
8
+
9
+ class AbstractProtocol # :nodoc:
10
+ attr :container_class
11
+
12
+ def initialize(container_class)
13
+ @container_class = container_class
14
+ end
15
+
16
+ def unmarshal_request(protocol_request)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def marshal_response(protocol_request, return_value)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def marshal_exception(exception)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def self.create_protocol_request(container_class, action_pack_request)
29
+ nil
30
+ end
31
+
32
+ def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
33
+ nil
34
+ end
35
+ end
36
+
37
+ class AbstractProtocolMessage # :nodoc:
38
+ attr_accessor :signature
39
+ attr_accessor :return_signature
40
+ attr_accessor :type
41
+ attr :options
42
+
43
+ def initialize(options={})
44
+ @signature = @return_signature = nil
45
+ @options = options
46
+ @type = @options[:type] || CheckedMessage
47
+ end
48
+
49
+ def signature=(value)
50
+ return if value.nil?
51
+ @signature = []
52
+ value.each do |klass|
53
+ if klass.is_a?(Hash)
54
+ @signature << klass.values.shift
55
+ else
56
+ @signature << klass
57
+ end
58
+ end
59
+ @signature
60
+ end
61
+
62
+ def checked?
63
+ @type == CheckedMessage
64
+ end
65
+
66
+ def check_parameter_types(values, signature)
67
+ return unless checked? && signature
68
+ unless signature.length == values.length
69
+ raise(ProtocolError, "Signature and parameter lengths mismatch")
70
+ end
71
+ (1..signature.length).each do |i|
72
+ check_compatibility(signature[i-1], values[i-1].class)
73
+ end
74
+ end
75
+
76
+ def check_compatibility(expected_class, received_class)
77
+ return if \
78
+ (expected_class == TrueClass or expected_class == FalseClass) and \
79
+ (received_class == TrueClass or received_class == FalseClass)
80
+ unless received_class.ancestors.include?(expected_class) or \
81
+ expected_class.ancestors.include?(received_class)
82
+ raise(ProtocolError, "value of type #{received_class.name} is not " +
83
+ "compatible with expected type #{expected_class.name}")
84
+ end
85
+ end
86
+ end
87
+
88
+ class ProtocolRequest < AbstractProtocolMessage # :nodoc:
89
+ attr :protocol
90
+ attr :raw_body
91
+
92
+ attr_accessor :web_service_name
93
+ attr_accessor :public_method_name
94
+ attr_accessor :content_type
95
+
96
+ def initialize(protocol, raw_body, web_service_name, public_method_name, content_type, options={})
97
+ super(options)
98
+ @protocol = protocol
99
+ @raw_body = raw_body
100
+ @web_service_name = web_service_name
101
+ @public_method_name = public_method_name
102
+ @content_type = content_type
103
+ end
104
+
105
+ def unmarshal
106
+ @protocol.unmarshal_request(self)
107
+ end
108
+
109
+ def marshal(return_value)
110
+ @protocol.marshal_response(self, return_value)
111
+ end
112
+ end
113
+
114
+ class ProtocolResponse < AbstractProtocolMessage # :nodoc:
115
+ attr :protocol
116
+ attr :raw_body
117
+
118
+ attr_accessor :content_type
119
+
120
+ def initialize(protocol, raw_body, content_type, options={})
121
+ super(options)
122
+ @protocol = protocol
123
+ @raw_body = raw_body
124
+ @content_type = content_type
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,55 @@
1
+ module ActionWebService # :nodoc:
2
+ module Protocol # :nodoc:
3
+ HeaderAndBody = :header_and_body
4
+ BodyOnly = :body_only
5
+
6
+ module Registry # :nodoc:
7
+ def self.append_features(base) # :nodoc:
8
+ super
9
+ base.extend(ClassMethods)
10
+ base.send(:include, ActionWebService::Protocol::Registry::InstanceMethods)
11
+ end
12
+
13
+ module ClassMethods # :nodoc:
14
+ def register_protocol(type, klass) # :nodoc:
15
+ case type
16
+ when HeaderAndBody
17
+ write_inheritable_array("header_and_body_protocols", [klass])
18
+ when BodyOnly
19
+ write_inheritable_array("body_only_protocols", [klass])
20
+ else
21
+ raise(ProtocolError, "unknown protocol type #{type}")
22
+ end
23
+ end
24
+ end
25
+
26
+ module InstanceMethods # :nodoc:
27
+ private
28
+ def probe_request_protocol(action_pack_request)
29
+ (header_and_body_protocols + body_only_protocols).each do |protocol|
30
+ protocol_request = protocol.create_protocol_request(self.class, action_pack_request)
31
+ return protocol_request if protocol_request
32
+ end
33
+ raise(ProtocolError, "unsupported request message format")
34
+ end
35
+
36
+ def probe_protocol_client(api, protocol_name, endpoint_uri, options)
37
+ (header_and_body_protocols + body_only_protocols).each do |protocol|
38
+ protocol_client = protocol.create_protocol_client(api, protocol_name, endpoint_uri, options)
39
+ return protocol_client if protocol_client
40
+ end
41
+ raise(ProtocolError, "unsupported client protocol :#{protocol_name}")
42
+ end
43
+
44
+ def header_and_body_protocols
45
+ self.class.read_inheritable_attribute("header_and_body_protocols") || []
46
+ end
47
+
48
+ def body_only_protocols
49
+ self.class.read_inheritable_attribute("body_only_protocols") || []
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,484 @@
1
+ require 'soap/processor'
2
+ require 'soap/mapping'
3
+ require 'soap/rpc/element'
4
+ require 'xsd/datatypes'
5
+ require 'xsd/ns'
6
+ require 'singleton'
7
+
8
+ module ActionWebService # :nodoc:
9
+ module Protocol # :nodoc:
10
+ module Soap # :nodoc:
11
+ class ProtocolError < ActionWebService::ActionWebServiceError # :nodoc:
12
+ end
13
+
14
+ def self.append_features(base) # :nodoc:
15
+ super
16
+ base.register_protocol(HeaderAndBody, SoapProtocol)
17
+ base.extend(ClassMethods)
18
+ base.wsdl_service_name('ActionWebService')
19
+ end
20
+
21
+ module ClassMethods
22
+ # Specifies the WSDL service name to use when generating WSDL. Highly
23
+ # recommended that you set this value, or code generators may generate
24
+ # classes with very generic names.
25
+ #
26
+ # === Example
27
+ # class MyController < ActionController::Base
28
+ # wsdl_service_name 'MyService'
29
+ # end
30
+ def wsdl_service_name(name)
31
+ write_inheritable_attribute("soap_mapper", SoapMapper.new("urn:#{name}"))
32
+ end
33
+
34
+ def soap_mapper # :nodoc:
35
+ read_inheritable_attribute("soap_mapper")
36
+ end
37
+ end
38
+
39
+ class SoapProtocol < AbstractProtocol # :nodoc:
40
+ attr :mapper
41
+
42
+ def initialize(mapper)
43
+ @mapper = mapper
44
+ end
45
+
46
+ def self.create_protocol_request(container_class, action_pack_request)
47
+ soap_action = extract_soap_action(action_pack_request)
48
+ return nil unless soap_action
49
+ service_name = action_pack_request.parameters['action']
50
+ public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1]
51
+ content_type = action_pack_request.env['HTTP_CONTENT_TYPE']
52
+ content_type ||= 'text/xml'
53
+ protocol = SoapProtocol.new(container_class.soap_mapper)
54
+ ProtocolRequest.new(protocol,
55
+ action_pack_request.raw_post,
56
+ service_name.to_sym,
57
+ public_method_name,
58
+ content_type)
59
+ end
60
+
61
+ def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
62
+ return nil unless protocol_name.to_s.downcase.to_sym == :soap
63
+ ActionWebService::Client::Soap.new(api, endpoint_uri, options)
64
+ end
65
+
66
+ def unmarshal_request(protocol_request)
67
+ unmarshal = lambda do
68
+ envelope = SOAP::Processor.unmarshal(protocol_request.raw_body)
69
+ request = envelope.body.request
70
+ values = request.collect{|k, v| request[k]}
71
+ soap_to_ruby_array(values)
72
+ end
73
+ signature = protocol_request.signature
74
+ if signature
75
+ map_signature_types(signature)
76
+ values = unmarshal.call
77
+ signature = signature.map{|x|mapper.lookup(x).ruby_klass}
78
+ protocol_request.check_parameter_types(values, signature)
79
+ values
80
+ else
81
+ if protocol_request.checked?
82
+ []
83
+ else
84
+ unmarshal.call
85
+ end
86
+ end
87
+ end
88
+
89
+ def marshal_response(protocol_request, return_value)
90
+ marshal = lambda do |signature|
91
+ mapping = mapper.lookup(signature[0])
92
+ return_value = fixup_array_types(mapping, return_value)
93
+ signature = signature.map{|x|mapper.lookup(x).ruby_klass}
94
+ protocol_request.check_parameter_types([return_value], signature)
95
+ param_def = [['retval', 'return', mapping.registry_mapping]]
96
+ [param_def, ruby_to_soap(return_value)]
97
+ end
98
+ signature = protocol_request.return_signature
99
+ param_def = nil
100
+ if signature
101
+ param_def, return_value = marshal.call(signature)
102
+ else
103
+ if protocol_request.checked?
104
+ param_def, return_value = nil, nil
105
+ else
106
+ param_def, return_value = marshal.call([return_value.class])
107
+ end
108
+ end
109
+ qname = XSD::QName.new(mapper.custom_namespace,
110
+ protocol_request.public_method_name)
111
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
112
+ response.retval = return_value unless return_value.nil?
113
+ ProtocolResponse.new(self, create_response(response), 'text/xml')
114
+ end
115
+
116
+ def marshal_exception(exc)
117
+ ProtocolResponse.new(self, create_exception_response(exc), 'text/xml')
118
+ end
119
+
120
+ private
121
+ def self.extract_soap_action(request)
122
+ return nil unless request.method == :post
123
+ content_type = request.env['HTTP_CONTENT_TYPE'] || 'text/xml'
124
+ return nil unless content_type
125
+ soap_action = request.env['HTTP_SOAPACTION']
126
+ return nil unless soap_action
127
+ soap_action.gsub!(/^"/, '')
128
+ soap_action.gsub!(/"$/, '')
129
+ soap_action.strip!
130
+ return nil if soap_action.empty?
131
+ soap_action
132
+ end
133
+
134
+ def fixup_array_types(mapping, obj)
135
+ mapping.each_attribute do |name, type, attr_mapping|
136
+ if attr_mapping.custom_type?
137
+ attr_obj = obj.send(name)
138
+ new_obj = fixup_array_types(attr_mapping, attr_obj)
139
+ obj.send("#{name}=", new_obj) unless new_obj.equal?(attr_obj)
140
+ end
141
+ end
142
+ if mapping.is_a?(SoapArrayMapping)
143
+ obj = mapping.ruby_klass.new(obj)
144
+ # man, this is going to be slow for big arrays :(
145
+ (1..obj.size).each do |i|
146
+ i -= 1
147
+ obj[i] = fixup_array_types(mapping.element_mapping, obj[i])
148
+ end
149
+ else
150
+ if !mapping.generated_klass.nil? && mapping.generated_klass.respond_to?(:members)
151
+ # have to map the publically visible structure of the class
152
+ new_obj = mapping.generated_klass.new
153
+ mapping.generated_klass.members.each do |name, klass|
154
+ new_obj.send("#{name}=", obj.send(name))
155
+ end
156
+ obj = new_obj
157
+ end
158
+ end
159
+ obj
160
+ end
161
+
162
+ def map_signature_types(types)
163
+ types.collect{|type| mapper.map(type)}
164
+ end
165
+
166
+ def create_response(body)
167
+ header = SOAP::SOAPHeader.new
168
+ body = SOAP::SOAPBody.new(body)
169
+ envelope = SOAP::SOAPEnvelope.new(header, body)
170
+ SOAP::Processor.marshal(envelope)
171
+ end
172
+
173
+ def create_exception_response(exc)
174
+ detail = SOAP::Mapping::SOAPException.new(exc)
175
+ body = SOAP::SOAPFault.new(
176
+ SOAP::SOAPString.new('Server'),
177
+ SOAP::SOAPString.new(exc.to_s),
178
+ SOAP::SOAPString.new(self.class.name),
179
+ SOAP::Mapping.obj2soap(detail))
180
+ create_response(body)
181
+ end
182
+
183
+ def ruby_to_soap(obj)
184
+ SOAP::Mapping.obj2soap(obj, mapper.registry)
185
+ end
186
+
187
+ def soap_to_ruby(obj)
188
+ SOAP::Mapping.soap2obj(obj, mapper.registry)
189
+ end
190
+
191
+ def soap_to_ruby_array(array)
192
+ array.map{|x| soap_to_ruby(x)}
193
+ end
194
+ end
195
+
196
+ class SoapMapper # :nodoc:
197
+ attr :registry
198
+ attr :custom_namespace
199
+ attr :custom_types
200
+
201
+ def initialize(custom_namespace)
202
+ @custom_namespace = custom_namespace
203
+ @registry = SOAP::Mapping::Registry.new
204
+ @klass2map = {}
205
+ @custom_types = {}
206
+ @ar2klass = {}
207
+ end
208
+
209
+ def lookup(klass)
210
+ lookup_klass = klass.is_a?(Array) ? klass[0] : klass
211
+ generated_klass = nil
212
+ unless lookup_klass.respond_to?(:ancestors)
213
+ raise(ProtocolError, "expected parameter type definition to be a Class")
214
+ end
215
+ if lookup_klass.ancestors.include?(ActiveRecord::Base)
216
+ generated_klass = @ar2klass.has_key?(klass) ? @ar2klass[klass] : nil
217
+ klass = generated_klass if generated_klass
218
+ end
219
+ return @klass2map[klass] if @klass2map.has_key?(klass)
220
+
221
+ custom_type = false
222
+
223
+ ruby_klass = select_class(lookup_klass)
224
+ generated_klass = @ar2klass[lookup_klass] if @ar2klass.has_key?(lookup_klass)
225
+ type_name = ruby_klass.name
226
+
227
+ # Array signatures generate a double-mapping and require generation
228
+ # of an Array subclass to represent the mapping in the SOAP
229
+ # registry
230
+ array_klass = nil
231
+ if klass.is_a?(Array)
232
+ array_klass = Class.new(Array) do
233
+ module_eval <<-END
234
+ def self.name
235
+ "#{type_name}Array"
236
+ end
237
+ END
238
+ end
239
+ end
240
+
241
+ mapping = @registry.find_mapped_soap_class(ruby_klass) rescue nil
242
+ unless mapping
243
+ # Custom structured type, generate a mapping
244
+ info = { :type => XSD::QName.new(@custom_namespace, type_name) }
245
+ @registry.add(ruby_klass,
246
+ SOAP::SOAPStruct,
247
+ SOAP::Mapping::Registry::TypedStructFactory,
248
+ info)
249
+ mapping = ensure_mapped(ruby_klass)
250
+ custom_type = true
251
+ end
252
+
253
+ array_mapping = nil
254
+ if array_klass
255
+ # Typed array always requires a custom type. The info of the array
256
+ # is the info of its element type (in mapping[2]), falling back
257
+ # to SOAP base types.
258
+ info = mapping[2]
259
+ info ||= {}
260
+ info[:type] ||= soap_base_type_qname(mapping[0])
261
+ @registry.add(array_klass,
262
+ SOAP::SOAPArray,
263
+ SOAP::Mapping::Registry::TypedArrayFactory,
264
+ info)
265
+ array_mapping = ensure_mapped(array_klass)
266
+ end
267
+
268
+ if array_mapping
269
+ @klass2map[ruby_klass] = SoapMapping.new(self,
270
+ type_name,
271
+ ruby_klass,
272
+ generated_klass,
273
+ mapping[0],
274
+ mapping,
275
+ custom_type)
276
+ @klass2map[klass] = SoapArrayMapping.new(self,
277
+ type_name,
278
+ array_klass,
279
+ array_mapping[0],
280
+ array_mapping,
281
+ @klass2map[ruby_klass])
282
+ @custom_types[klass] = @klass2map[klass]
283
+ @custom_types[ruby_klass] = @klass2map[ruby_klass] if custom_type
284
+ else
285
+ @klass2map[klass] = SoapMapping.new(self,
286
+ type_name,
287
+ ruby_klass,
288
+ generated_klass,
289
+ mapping[0],
290
+ mapping,
291
+ custom_type)
292
+ @custom_types[klass] = @klass2map[klass] if custom_type
293
+ end
294
+
295
+ @klass2map[klass]
296
+ end
297
+ alias :map :lookup
298
+
299
+ def map_container_services(container, &block)
300
+ dispatching_mode = container.web_service_dispatching_mode
301
+ web_services = nil
302
+ case dispatching_mode
303
+ when :direct
304
+ api = container.class.web_service_api
305
+ if container.respond_to?(:controller_class_name)
306
+ web_service_name = container.controller_class_name.sub(/Controller$/, '').underscore
307
+ else
308
+ web_service_name = container.class.name.demodulize.underscore
309
+ end
310
+ web_services = { web_service_name => api }
311
+ when :delegated
312
+ web_services = {}
313
+ container.class.web_services.each do |web_service_name, web_service_info|
314
+ begin
315
+ object = container.web_service_object(web_service_name)
316
+ rescue Exception => e
317
+ raise(ProtocolError, "failed to retrieve web service object for web service '#{web_service_name}': #{e.message}")
318
+ end
319
+ web_services[web_service_name] = object.class.web_service_api
320
+ end
321
+ end
322
+ web_services.each do |web_service_name, api|
323
+ if api.nil?
324
+ raise(ProtocolError, "no web service API set while in :#{dispatching_mode} mode")
325
+ end
326
+ map_api(api) do |api_methods|
327
+ yield web_service_name, api, api_methods if block_given?
328
+ end
329
+ end
330
+ end
331
+
332
+ def map_api(api, &block)
333
+ lookup_proc = lambda do |klass|
334
+ mapping = lookup(klass)
335
+ custom_mapping = nil
336
+ if mapping.respond_to?(:element_mapping)
337
+ custom_mapping = mapping.element_mapping
338
+ else
339
+ custom_mapping = mapping
340
+ end
341
+ if custom_mapping && custom_mapping.custom_type?
342
+ # What gives? This is required so that structure types
343
+ # referenced only by structures (and not signatures) still
344
+ # have a custom type mapping in the registry (needed for WSDL
345
+ # generation).
346
+ custom_mapping.each_attribute{}
347
+ end
348
+ mapping
349
+ end
350
+ api_methods = block.nil?? nil : {}
351
+ api.api_methods.each do |method_name, method_info|
352
+ expects = method_info[:expects]
353
+ expects_signature = nil
354
+ if expects
355
+ expects_signature = block ? [] : nil
356
+ expects.each do |klass|
357
+ lookup_klass = nil
358
+ if klass.is_a?(Hash)
359
+ lookup_klass = lookup_proc.call(klass.values[0])
360
+ expects_signature << {klass.keys[0]=>lookup_klass} if block
361
+ else
362
+ lookup_klass = lookup_proc.call(klass)
363
+ expects_signature << lookup_klass if block
364
+ end
365
+ end
366
+ end
367
+ returns = method_info[:returns]
368
+ returns_signature = returns ? returns.map{|klass| lookup_proc.call(klass)} : nil
369
+ if block
370
+ api_methods[method_name] = {
371
+ :expects => expects_signature,
372
+ :returns => returns_signature
373
+ }
374
+ end
375
+ end
376
+ yield api_methods if block
377
+ end
378
+
379
+ private
380
+ def select_class(klass)
381
+ return Integer if klass == Fixnum
382
+ if klass.ancestors.include?(ActiveRecord::Base)
383
+ new_klass = Class.new(ActionWebService::Struct)
384
+ new_klass.class_eval <<-EOS
385
+ def self.name
386
+ "#{klass.name}"
387
+ end
388
+ EOS
389
+ klass.columns.each do |column|
390
+ next if column.klass.nil?
391
+ new_klass.send(:member, column.name.to_sym, column.klass)
392
+ end
393
+ @ar2klass[klass] = new_klass
394
+ return new_klass
395
+ end
396
+ klass
397
+ end
398
+
399
+ def ensure_mapped(klass)
400
+ mapping = @registry.find_mapped_soap_class(klass) rescue nil
401
+ raise(ProtocolError, "failed to register #{klass.name}") unless mapping
402
+ mapping
403
+ end
404
+
405
+ def soap_base_type_qname(base_type)
406
+ xsd_type = base_type.ancestors.find{|c| c.const_defined? 'Type'}
407
+ xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
408
+ end
409
+ end
410
+
411
+ class SoapMapping # :nodoc:
412
+ attr :ruby_klass
413
+ attr :generated_klass
414
+ attr :soap_klass
415
+ attr :registry_mapping
416
+
417
+ def initialize(mapper, type_name, ruby_klass, generated_klass, soap_klass, registry_mapping,
418
+ custom_type=false)
419
+ @mapper = mapper
420
+ @type_name = type_name
421
+ @ruby_klass = ruby_klass
422
+ @generated_klass = generated_klass
423
+ @soap_klass = soap_klass
424
+ @registry_mapping = registry_mapping
425
+ @custom_type = custom_type
426
+ end
427
+
428
+ def type_name
429
+ @type_name
430
+ end
431
+
432
+ def custom_type?
433
+ @custom_type
434
+ end
435
+
436
+ def qualified_type_name
437
+ name = type_name
438
+ if custom_type?
439
+ "typens:#{name}"
440
+ else
441
+ xsd_type_for(@soap_klass)
442
+ end
443
+ end
444
+
445
+ def each_attribute(&block)
446
+ if @ruby_klass.respond_to?(:members)
447
+ @ruby_klass.members.each do |name, klass|
448
+ name = name.to_s
449
+ mapping = @mapper.lookup(klass)
450
+ yield name, mapping.qualified_type_name, mapping
451
+ end
452
+ end
453
+ end
454
+
455
+ def is_xsd_type?(klass)
456
+ klass.ancestors.include?(XSD::NSDBase)
457
+ end
458
+
459
+ def xsd_type_for(klass)
460
+ ns = XSD::NS.new
461
+ ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
462
+ xsd_klass = klass.ancestors.find{|c| c.const_defined?('Type')}
463
+ return ns.name(XSD::AnyTypeName) unless xsd_klass
464
+ ns.name(xsd_klass.const_get('Type'))
465
+ end
466
+ end
467
+
468
+ class SoapArrayMapping < SoapMapping # :nodoc:
469
+ attr :element_mapping
470
+
471
+ def initialize(mapper, type_name, ruby_klass, soap_klass, registry_mapping, element_mapping)
472
+ super(mapper, type_name, ruby_klass, nil, soap_klass, registry_mapping, true)
473
+ @element_mapping = element_mapping
474
+ end
475
+
476
+ def type_name
477
+ super + "Array"
478
+ end
479
+
480
+ def each_attribute(&block); end
481
+ end
482
+ end
483
+ end
484
+ end