PageRenderTime 148ms CodeModel.GetById 101ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 1ms

/tools/Ruby/lib/ruby/1.8/soap/wsdlDriver.rb

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