Welcome to the "trac"-ing site of soap4r!
[soap4r] [httpclient] [openpgp4u] [pkcs1] [logger] [csv] [vtr]

root/branches/1_5/lib/soap/wsdlDriver.rb

Revision 2001, 17.3 kB (checked in by nahi, 1 year ago)
  • wsdl2ruby.rb did not generate proper definitions for overloaded method in WSDL. closes #446.
  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 # SOAP4R - SOAP WSDL driver
2 # Copyright (C) 2000-2007  NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
4 # This program is copyrighted free software by NAKAMURA, Hiroshi.  You can
5 # redistribute it and/or modify it under the same terms of Ruby's license;
6 # either the dual license version in 2003, or any later version.
7
8
9 require 'wsdl/parser'
10 require 'wsdl/importer'
11 require 'xsd/qname'
12 require 'xsd/codegen/gensupport'
13 require 'soap/mapping/wsdlencodedregistry'
14 require 'soap/mapping/wsdlliteralregistry'
15 require 'soap/rpc/driver'
16 require 'wsdl/soap/methodDefCreator'
17 require 'wsdl/soap/classDefCreatorSupport'
18 require 'wsdl/soap/classNameCreator'
19
20
21 module SOAP
22
23
24 class WSDLDriverFactory
25   include WSDL::SOAP::ClassDefCreatorSupport
26
27   class FactoryError < StandardError; end
28
29   attr_reader :wsdl
30
31   def initialize(wsdl)
32     @wsdl = import(wsdl)
33     name_creator = WSDL::SOAP::ClassNameCreator.new
34     @modulepath = 'WSDLDriverFactory'
35     @methoddefcreator =
36       WSDL::SOAP::MethodDefCreator.new(@wsdl, name_creator, @modulepath, {})
37   end
38  
39   def inspect
40     sprintf("#<%s:%s:0x%x\n\n%s>", self.class.name, @wsdl.name, __id__, dump_method_signatures)
41   end
42
43   def create_rpc_driver(servicename = nil, portname = nil)
44     port = find_port(servicename, portname)
45     drv = SOAP::RPC::Driver.new(port.soap_address.location)
46     init_driver(drv, port)
47     add_operation(drv, port)
48     drv
49   end
50
51   # deprecated old interface
52   def create_driver(servicename = nil, portname = nil)
53     warn("WSDLDriverFactory#create_driver is deprecated.  Use create_rpc_driver instead.")
54     port = find_port(servicename, portname)
55     WSDLDriver.new(@wsdl, port, nil)
56   end
57
58   # Backward compatibility.
59   alias createDriver create_driver
60
61   def dump_method_signatures(servicename = nil, portname = nil)
62     targetservice = XSD::QName.new(@wsdl.targetnamespace, servicename) if servicename
63     targetport = XSD::QName.new(@wsdl.targetnamespace, portname) if portname
64     sig = []
65     element_definitions = @wsdl.collect_elements
66     @wsdl.services.each do |service|
67       next if targetservice and service.name != targetservice
68       service.ports.each do |port|
69         next if targetport and port.name != targetport
70         if porttype = port.porttype
71           assigned_method = collect_assigned_method(porttype.name)
72           if binding = port.porttype.find_binding
73             sig << binding.operations.collect { |op_bind|
74               operation = op_bind.find_operation
75               name = assigned_method[op_bind.boundid] || op_bind.name
76               str = "= #{safemethodname(name)}\n\n"
77               str << dump_method_signature(name, operation, element_definitions)
78               str.gsub(/^#/, " ")
79             }.join("\n")
80           end
81         end
82       end
83     end
84     sig.join("\n")
85   end
86
87 private
88
89   def collect_assigned_method(porttypename)
90     name_creator = WSDL::SOAP::ClassNameCreator.new
91     methoddefcreator =
92       WSDL::SOAP::MethodDefCreator.new(@wsdl, name_creator, nil, {})
93     methoddefcreator.dump(porttypename)
94     methoddefcreator.assigned_method
95   end
96
97   def find_port(servicename = nil, portname = nil)
98     service = port = nil
99     if servicename
100       service = @wsdl.service(
101         XSD::QName.new(@wsdl.targetnamespace, servicename))
102     else
103       service = @wsdl.services[0]
104     end
105     if service.nil?
106       raise FactoryError.new("service #{servicename} not found in WSDL")
107     end
108     if portname
109       port = service.ports[XSD::QName.new(@wsdl.targetnamespace, portname)]
110       if port.nil?
111         raise FactoryError.new("port #{portname} not found in WSDL")
112       end
113     else
114       port = service.ports.find { |port| !port.soap_address.nil? }
115       if port.nil?
116         raise FactoryError.new("no ports have soap:address")
117       end
118     end
119     if port.soap_address.nil?
120       raise FactoryError.new("soap:address element not found in WSDL")
121     end
122     port
123   end
124
125   def init_driver(drv, port)
126     wsdl_elements = @wsdl.collect_elements
127     wsdl_types = @wsdl.collect_complextypes + @wsdl.collect_simpletypes
128     rpc_decode_typemap = wsdl_types +
129       @wsdl.soap_rpc_complextypes(port.find_binding)
130     drv.proxy.mapping_registry =
131       Mapping::WSDLEncodedRegistry.new(rpc_decode_typemap)
132     drv.proxy.literal_mapping_registry =
133       Mapping::WSDLLiteralRegistry.new(wsdl_types, wsdl_elements)
134   end
135
136   def add_operation(drv, port)
137     port.find_binding.operations.each do |op_bind|
138       op_name = op_bind.soapoperation_name
139       soapaction = op_bind.soapaction || ''
140       orgname = op_name.name
141       name = XSD::CodeGen::GenSupport.safemethodname(orgname)
142       param_def = create_param_def(op_bind)
143       opt = {
144         :request_style => op_bind.soapoperation_style,
145         :response_style => op_bind.soapoperation_style,
146         :request_use => op_bind.soapbody_use_input,
147         :response_use => op_bind.soapbody_use_output
148       }
149       if op_bind.soapoperation_style == :rpc
150         drv.add_rpc_operation(op_name, soapaction, name, param_def, opt)
151       else
152         drv.add_document_operation(soapaction, name, param_def, opt)
153       end
154       if orgname != name and orgname.capitalize == name.capitalize
155         ::SOAP::Mapping.define_singleton_method(drv, orgname) do |*arg|
156           __send__(name, *arg)
157         end
158       end
159     end
160   end
161
162   def import(location)
163     WSDL::Importer.import(location)
164   end
165
166   def create_param_def(op_bind)
167     op = op_bind.find_operation
168     if op_bind.soapoperation_style == :rpc
169       param_def = @methoddefcreator.collect_rpcparameter(op)
170     else
171       param_def = @methoddefcreator.collect_documentparameter(op)
172     end
173     # the first element of typedef in param_def is a String like
174     # "::SOAP::SOAPStruct".  turn this String to a class.
175     param_def.collect { |io_type, name, param_type|
176       [io_type, name, ::SOAP::RPC::SOAPMethod.parse_param_type(param_type)]
177     }
178   end
179
180   def partqname(part)
181     if part.type
182       part.type
183     else
184       part.element
185     end
186   end
187
188   def param_def(type, name, klass, partqname)
189     [type, name, [klass, partqname.namespace, partqname.name]]
190   end
191
192   def filter_parts(partsdef, partssource)
193     parts = partsdef.split(/\s+/)
194     partssource.find_all { |part| parts.include?(part.name) }
195   end
196 end
197
198
199 class WSDLDriver
200   class << self
201     if RUBY_VERSION >= "1.7.0"
202       def __attr_proxy(symbol, assignable = false)
203         name = symbol.to_s
204         define_method(name) {
205           @servant.__send__(name)
206         }
207         if assignable
208           aname = name + '='
209           define_method(aname) { |rhs|
210             @servant.__send__(aname, rhs)
211           }
212         end
213       end
214     else
215       def __attr_proxy(symbol, assignable = false)
216         name = symbol.to_s
217         module_eval <<-EOS
218           def #{name}
219             @servant.#{name}
220           end
221         EOS
222         if assignable
223           module_eval <<-EOS
224             def #{name}=(value)
225               @servant.#{name} = value
226             end
227           EOS
228         end
229       end
230     end
231   end
232
233   __attr_proxy :options
234   __attr_proxy :headerhandler
235   __attr_proxy :streamhandler
236   __attr_proxy :test_loopback_response
237   __attr_proxy :endpoint_url, true
238   __attr_proxy :mapping_registry, true          # for RPC unmarshal
239   __attr_proxy :wsdl_mapping_registry, true     # for RPC marshal
240   __attr_proxy :default_encodingstyle, true
241   __attr_proxy :generate_explicit_type, true
242   __attr_proxy :allow_unqualified_element, true
243
244   def httpproxy
245     @servant.options["protocol.http.proxy"]
246   end
247
248   def httpproxy=(httpproxy)
249     @servant.options["protocol.http.proxy"] = httpproxy
250   end
251
252   def wiredump_dev
253     @servant.options["protocol.http.wiredump_dev"]
254   end
255
256   def wiredump_dev=(wiredump_dev)
257     @servant.options["protocol.http.wiredump_dev"] = wiredump_dev
258   end
259
260   def mandatorycharset
261     @servant.options["protocol.mandatorycharset"]
262   end
263
264   def mandatorycharset=(mandatorycharset)
265     @servant.options["protocol.mandatorycharset"] = mandatorycharset
266   end
267
268   def wiredump_file_base
269     @servant.options["protocol.wiredump_file_base"]
270   end
271
272   def wiredump_file_base=(wiredump_file_base)
273     @servant.options["protocol.wiredump_file_base"] = wiredump_file_base
274   end
275
276   def initialize(wsdl, port, logdev)
277     @servant = Servant__.new(self, wsdl, port, logdev)
278   end
279
280   def inspect
281     "#<#{self.class}:#{@servant.port.name}>"
282   end
283
284   def reset_stream
285     @servant.reset_stream
286   end
287
288   # Backward compatibility.
289   alias generateEncodeType= generate_explicit_type=
290
291   class Servant__
292     include SOAP
293
294     attr_reader :options
295     attr_reader :port
296
297     attr_accessor :soapaction
298     attr_accessor :default_encodingstyle
299     attr_accessor :allow_unqualified_element
300     attr_accessor :generate_explicit_type
301     attr_accessor :mapping_registry
302     attr_accessor :wsdl_mapping_registry
303
304     def initialize(host, wsdl, port, logdev)
305       @host = host
306       @wsdl = wsdl
307       @port = port
308       @logdev = logdev
309       @soapaction = nil
310       @options = setup_options
311       @default_encodingstyle = nil
312       @allow_unqualified_element = nil
313       @generate_explicit_type = false
314       @mapping_registry = nil           # for rpc unmarshal
315       @wsdl_mapping_registry = nil      # for rpc marshal
316       @wiredump_file_base = nil
317       @mandatorycharset = nil
318       @wsdl_elements = @wsdl.collect_elements
319       @wsdl_types = @wsdl.collect_complextypes + @wsdl.collect_simpletypes
320       @rpc_decode_typemap = @wsdl_types +
321         @wsdl.soap_rpc_complextypes(port.find_binding)
322       @wsdl_mapping_registry = Mapping::WSDLEncodedRegistry.new(
323         @rpc_decode_typemap)
324       @doc_mapper = Mapping::WSDLLiteralRegistry.new(
325         @wsdl_types, @wsdl_elements)
326       endpoint_url = @port.soap_address.location
327       # Convert a map which key is QName, to a Hash which key is String.
328       @operation = {}
329       @port.inputoperation_map.each do |op_name, op_info|
330         orgname = op_name.name
331         name = XSD::CodeGen::GenSupport.safemethodname(orgname)
332         @operation[name] = @operation[orgname] = op_info
333         add_method_interface(op_info)
334       end
335       @proxy = ::SOAP::RPC::Proxy.new(endpoint_url, @soapaction, @options)
336     end
337
338     def inspect
339       "#<#{self.class}:#{@proxy.inspect}>"
340     end
341
342     def endpoint_url
343       @proxy.endpoint_url
344     end
345
346     def endpoint_url=(endpoint_url)
347       @proxy.endpoint_url = endpoint_url
348     end
349
350     def headerhandler
351       @proxy.headerhandler
352     end
353
354     def streamhandler
355       @proxy.streamhandler
356     end
357
358     def test_loopback_response
359       @proxy.test_loopback_response
360     end
361
362     def reset_stream
363       @proxy.reset_stream
364     end
365
366     def rpc_call(name, *values)
367       set_wiredump_file_base(name)
368       unless op_info = @operation[name]
369         raise RuntimeError, "method: #{name} not defined"
370       end
371       req_header = create_request_header
372       req_body = create_request_body(op_info, *values)
373       reqopt = create_options({
374         :soapaction => op_info.soapaction || @soapaction})
375       resopt = create_options({
376         :decode_typemap => @rpc_decode_typemap})
377       env = @proxy.route(req_header, req_body, reqopt, resopt)
378       raise EmptyResponseError unless env
379       receive_headers(env.header)
380       begin
381         @proxy.check_fault(env.body)
382       rescue ::SOAP::FaultError => e
383         Mapping.fault2exception(e)
384       end
385       ret = env.body.response ?
386         Mapping.soap2obj(env.body.response, @mapping_registry) : nil
387       if env.body.outparams
388         outparams = env.body.outparams.collect { |outparam|
389           Mapping.soap2obj(outparam)
390         }
391         return [ret].concat(outparams)
392       else
393         return ret
394       end
395     end
396
397     # req_header: [[element, mustunderstand, encodingstyle(QName/String)], ...]
398     # req_body: SOAPBasetype/SOAPCompoundtype
399     def document_send(name, header_obj, body_obj)
400       set_wiredump_file_base(name)
401       unless op_info = @operation[name]
402         raise RuntimeError, "method: #{name} not defined"
403       end
404       req_header = header_obj ? header_from_obj(header_obj, op_info) : nil
405       req_body = body_from_obj(body_obj, op_info)
406       opt = create_options({
407         :soapaction => op_info.soapaction || @soapaction,
408         :decode_typemap => @wsdl_types})
409       env = @proxy.invoke(req_header, req_body, opt)
410       raise EmptyResponseError unless env
411       if env.body.fault
412         raise ::SOAP::FaultError.new(env.body.fault)
413       end
414       res_body_obj = env.body.response ?
415         Mapping.soap2obj(env.body.response, @mapping_registry) : nil
416       return env.header, res_body_obj
417     end
418
419   private
420
421     def create_options(hash = nil)
422       opt = {}
423       opt[:default_encodingstyle] = @default_encodingstyle
424       opt[:allow_unqualified_element] = @allow_unqualified_element
425       opt[:generate_explicit_type] = @generate_explicit_type
426       opt.update(hash) if hash
427       opt
428     end
429
430     def set_wiredump_file_base(name)
431       if @wiredump_file_base
432         @proxy.set_wiredump_file_base(@wiredump_file_base + "_#{name}")
433       end
434     end
435
436     def create_request_header
437       header = SOAPHeader.new
438       items = @proxy.headerhandler.on_outbound(header)
439       items.each do |item|
440         header.add(item.elename.name, item)
441       end
442       header
443     end
444
445     def receive_headers(header)
446       @proxy.headerhandler.on_inbound(header) if header
447     end
448
449     def create_request_body(op_info, *values)
450       method = create_method_struct(op_info, *values)
451       SOAPBody.new(method)
452     end
453
454     def create_method_struct(op_info, *params)
455       parts_names = op_info.bodyparts.collect { |part| part.name }
456       obj = create_method_obj(parts_names, params)
457       method = Mapping.obj2soap(obj, @wsdl_mapping_registry, op_info.op_name)
458       if method.members.size != parts_names.size
459         new_method = SOAPStruct.new
460         method.each do |key, value|
461           if parts_names.include?(key)
462             new_method.add(key, value)
463           end
464         end
465         method = new_method
466       end
467       method.elename = op_info.op_name
468       method.type = XSD::QName.new      # Request should not be typed.
469       method
470     end
471
472     def create_method_obj(names, params)
473       o = Object.new
474       idx = 0
475       while idx < params.length
476         o.instance_variable_set('@' + names[idx], params[idx])
477         idx += 1
478       end
479       o
480     end
481
482     def header_from_obj(obj, op_info)
483       if obj.is_a?(SOAPHeader)
484         obj
485       elsif op_info.headerparts.empty?
486         if obj.nil?
487           nil
488         else
489           raise RuntimeError.new("no header definition in schema: #{obj}")
490         end
491       elsif op_info.headerparts.size == 1
492         part = op_info.headerparts[0]
493         header = SOAPHeader.new()
494         header.add(headeritem_from_obj(obj, part.element || part.eletype))
495         header
496       else
497         header = SOAPHeader.new()
498         op_info.headerparts.each do |part|
499           child = Mapping.get_attribute(obj, part.name)
500           ele = headeritem_from_obj(child, part.element || part.eletype)
501           header.add(part.name, ele)
502         end
503         header
504       end
505     end
506
507     def headeritem_from_obj(obj, name)
508       if obj.nil?
509         SOAPElement.new(name)
510       elsif obj.is_a?(SOAPHeaderItem)
511         obj
512       else
513         Mapping.obj2soap(obj, @doc_mapper, name)
514       end
515     end
516
517     def body_from_obj(obj, op_info)
518       if obj.is_a?(SOAPBody)
519         obj
520       elsif op_info.bodyparts.empty?
521         if obj.nil?
522           nil
523         else
524           raise RuntimeError.new("no body found in schema")
525         end
526       elsif op_info.bodyparts.size == 1
527         part = op_info.bodyparts[0]
528         ele = bodyitem_from_obj(obj, part.element || part.type)
529         SOAPBody.new(ele)
530       else
531         body = SOAPBody.new
532         op_info.bodyparts.each do |part|
533           child = Mapping.get_attribute(obj, part.name)
534           ele = bodyitem_from_obj(child, part.element || part.type)
535           body.add(ele.elename.name, ele)
536         end
537         body
538       end
539     end
540
541     def bodyitem_from_obj(obj, name)
542       if obj.nil?
543         SOAPElement.new(name)
544       elsif obj.is_a?(SOAPElement)
545         obj
546       else
547         Mapping.obj2soap(obj, @doc_mapper, name)
548       end
549     end
550
551     def add_method_interface(op_info)
552       name = XSD::CodeGen::GenSupport.safemethodname(op_info.op_name.name)
553       orgname = op_info.op_name.name
554       parts_names = op_info.bodyparts.collect { |part| part.name }
555       case op_info.style
556       when :document
557         if orgname != name and orgname.capitalize == name.capitalize
558           add_document_method_interface(orgname, parts_names)
559         end
560         add_document_method_interface(name, parts_names)
561       when :rpc
562         if orgname != name and orgname.capitalize == name.capitalize
563           add_rpc_method_interface(orgname, parts_names)
564         end
565         add_rpc_method_interface(name, parts_names)
566       else
567         raise RuntimeError.new("unknown style: #{op_info.style}")
568       end
569     end
570
571     def add_rpc_method_interface(name, parts_names)
572       param_count = parts_names.size
573       @host.instance_eval <<-EOS
574         def #{name}(*arg)
575           unless arg.size == #{param_count}
576             raise ArgumentError.new(
577               "wrong number of arguments (\#{arg.size} for #{param_count})")
578           end
579           @servant.rpc_call(#{name.dump}, *arg)
580         end
581       EOS
582       @host.method(name)
583     end
584
585     def add_document_method_interface(name, parts_names)
586       @host.instance_eval <<-EOS
587         def #{name}(h, b)
588           @servant.document_send(#{name.dump}, h, b)
589         end
590       EOS
591       @host.method(name)
592     end
593
594     def setup_options
595       if opt = Property.loadproperty(::SOAP::PropertyName)
596         opt = opt["client"]
597       end
598       opt ||= Property.new
599       opt.add_hook("protocol.mandatorycharset") do |key, value|
600         @mandatorycharset = value
601       end
602       opt.add_hook("protocol.wiredump_file_base") do |key, value|
603         @wiredump_file_base = value
604       end
605       opt["protocol.http.charset"] ||= XSD::Charset.xml_encoding_label
606       opt["protocol.http.proxy"] ||= Env::HTTP_PROXY
607       opt["protocol.http.no_proxy"] ||= Env::NO_PROXY
608       opt
609     end
610   end
611 end
612
613
614 end
Note: See TracBrowser for help on using the browser.