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