diff options
author | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2003-09-24 15:18:44 +0000 |
---|---|---|
committer | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2003-09-24 15:18:44 +0000 |
commit | db9445103c082a306ba085f7677da02ea94b8841 (patch) | |
tree | a311d59f031ae5def87f68be71ed1f58abadc031 /lib/soap/wsdlDriver.rb | |
parent | 8c2fb77787d1f20b4c19c9c52552856c339b86e9 (diff) |
* lib/soap/* (29 files): SOAP4R added.
* lib/wsdl/* (42 files): WSDL4R added.
* lib/xsd/* (12 files): XSD4R added.
* test/soap/* (16 files): added.
* test/wsdl/* (2 files): added.
* test/xsd/* (3 files): added.
* sample/soap/* (27 files): added.
* sample/wsdl/* (13 files): added.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4591 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/soap/wsdlDriver.rb')
-rw-r--r-- | lib/soap/wsdlDriver.rb | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/lib/soap/wsdlDriver.rb b/lib/soap/wsdlDriver.rb new file mode 100644 index 0000000000..849effcb0b --- /dev/null +++ b/lib/soap/wsdlDriver.rb @@ -0,0 +1,490 @@ +=begin +SOAP4R - SOAP WSDL driver +Copyright (C) 2002, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'wsdl/parser' +require 'wsdl/importer' +require 'xsd/qname' +require 'soap/element' +require 'soap/baseData' +require 'soap/streamHandler' +require 'soap/mapping' +require 'soap/mapping/wsdlRegistry' +require 'soap/rpc/rpc' +require 'soap/rpc/element' +require 'soap/processor' +require 'logger' + + +module SOAP + + +class WSDLDriverFactory + class FactoryError < StandardError; end + + attr_reader :wsdl + + def initialize(wsdl, logdev = nil) + @logdev = logdev + @wsdl = import(wsdl) + end + + def create_driver(servicename = nil, portname = nil, opt = {}) + service = if servicename + @wsdl.service(XSD::QName.new(@wsdl.targetnamespace, servicename)) + else + @wsdl.services[0] + end + if service.nil? + raise FactoryError.new("Service #{ servicename } not found in WSDL.") + end + port = if portname + service.ports[XSD::QName.new(@wsdl.targetnamespace, portname)] + else + service.ports[0] + end + if port.nil? + raise FactoryError.new("Port #{ portname } not found in WSDL.") + end + if port.soap_address.nil? + raise FactoryError.new("soap:address element not found in WSDL.") + end + WSDLDriver.new(@wsdl, port, @logdev, opt) + end + + # Backward compatibility. + alias createDriver create_driver + +private + + def import(location) + WSDL::Importer.import(location) + end +end + + +class WSDLDriver + class << self + def __attr_proxy(symbol, assignable = false) + name = symbol.to_s + module_eval <<-EOD + def #{name} + @servant.#{name} + end + EOD + if assignable + module_eval <<-EOD + def #{name}=(rhs) + @servant.#{name} = rhs + end + EOD + end + end + end + + __attr_proxy :opt + __attr_proxy :logdev, true + __attr_proxy :mapping_registry, true # for RPC unmarshal + __attr_proxy :wsdl_mapping_registry, true # for RPC marshal + __attr_proxy :endpoint_url, true + __attr_proxy :wiredump_dev, true + __attr_proxy :wiredump_file_base, true + __attr_proxy :httpproxy, true + + __attr_proxy :default_encodingstyle, true + __attr_proxy :allow_unqualified_element, true + __attr_proxy :generate_explicit_type, true + + def reset_stream + @servant.reset_stream + end + + # Backward compatibility. + alias generateEncodeType= generate_explicit_type= + + class Servant__ + include Logger::Severity + include SOAP + + attr_reader :opt + attr_accessor :logdev + attr_accessor :mapping_registry + attr_accessor :wsdl_mapping_registry + attr_reader :endpoint_url + attr_reader :wiredump_dev + attr_reader :wiredump_file_base + attr_reader :httpproxy + + attr_accessor :default_encodingstyle + attr_accessor :allow_unqualified_element + attr_accessor :generate_explicit_type + + class Mapper + def initialize(elements, types) + @elements = elements + @types = types + end + + def obj2ele(obj, name) + if ele = @elements[name] + _obj2ele(obj, ele) + elsif type = @types[name] + obj2type(obj, type) + else + raise RuntimeError.new("Cannot find name #{name} in schema.") + end + end + + def ele2obj(ele, *arg) + raise NotImplementedError.new + end + + private + + def _obj2ele(obj, ele) + o = nil + if ele.type + if type = @types[ele.type] + o = obj2type(obj, type) + elsif type = TypeMap[ele.type] + o = base2soap(obj, type) + else + raise RuntimeError.new("Cannot find type #{ele.type}.") + end + o.elename = ele.name + elsif ele.local_complextype + o = SOAPElement.new(ele.name) + ele.local_complextype.each_element do |child_name, child_ele| + o.add(_obj2ele(find_attribute(obj, child_name.name), child_ele)) + end + else + raise RuntimeError.new("Illegal schema?") + end + o + end + + def obj2type(obj, type) + o = SOAPElement.new(type.name) + type.each_element do |child_name, child_ele| + o.add(_obj2ele(find_attribute(obj, child_name.name), child_ele)) + end + o + end + + def _ele2obj(ele) + raise NotImplementedError.new + end + + def base2soap(obj, type) + soap_obj = nil + if type <= XSD::XSDString + soap_obj = type.new(XSD::Charset.is_ces(obj, $KCODE) ? + XSD::Charset.encoding_conv(obj, $KCODE, XSD::Charset.encoding) : obj) + else + soap_obj = type.new(obj) + end + soap_obj + end + + def find_attribute(obj, attr_name) + if obj.respond_to?(attr_name) + obj.__send__(attr_name) + elsif obj.is_a?(Hash) + obj[attr_name] || obj[attr_name.intern] + else + obj.instance_eval("@#{ attr_name }") + end + end + end + + def initialize(host, wsdl, port, logdev, opt) + @host = host + @wsdl = wsdl + @port = port + @logdev = logdev + @opt = opt.dup + @mapping_registry = nil # for rpc unmarshal + @wsdl_mapping_registry = nil # for rpc marshal + @endpoint_url = nil + @wiredump_dev = nil + @wiredump_file_base = nil + @httpproxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] + + @wsdl_elements = @wsdl.collect_elements + @wsdl_types = @wsdl.collect_complextypes + @rpc_decode_typemap = @wsdl_types + @wsdl.soap_rpc_complextypes(port.find_binding) + @wsdl_mapping_registry = Mapping::WSDLRegistry.new(@rpc_decode_typemap) + @doc_mapper = Mapper.new(@wsdl_elements, @wsdl_types) + @default_encodingstyle = EncodingNamespace + @allow_unqualified_element = true + @generate_explicit_type = false + + create_handler + @operations = {} + # Convert a map which key is QName, to a Hash which key is String. + @port.inputoperation_map.each do |op_name, op_info| + @operations[op_name.name] = op_info + add_method_interface(op_info) + end + end + + def endpoint_url=(endpoint_url) + @endpoint_url = endpoint_url + if @handler + @handler.endpoint_url = @endpoint_url + @handler.reset + end + log(DEBUG) { "endpoint_url=: set endpoint_url #{ @endpoint_url }." } + end + + def wiredump_dev=(dev) + @wiredump_dev = dev + if @handler + @handler.wiredump_dev = @wiredump_dev + @handler.reset + end + end + + def wiredump_file_base=(base) + @wiredump_file_base = base + end + + def httpproxy=(httpproxy) + @httpproxy = httpproxy + if @handler + @handler.proxy = @httpproxy + @handler.reset + end + log(DEBUG) { "httpproxy=: set httpproxy #{ @httpproxy }." } + end + + def reset_stream + @handler.reset + end + + def rpc_send(method_name, *params) + log(INFO) { "call: calling method '#{ method_name }'." } + log(DEBUG) { "call: parameters '#{ params.inspect }'." } + + op_info = @operations[method_name] + parts_names = op_info.bodyparts.collect { |part| part.name } + obj = create_method_obj(parts_names, params) + method = Mapping.obj2soap(obj, @wsdl_mapping_registry, op_info.optype_name) + method.elename = op_info.op_name + method.type = XSD::QName.new # Request should not be typed. + req_header = nil + req_body = SOAPBody.new(method) + + if @wiredump_file_base + @handler.wiredump_file_base = @wiredump_file_base + '_' << method_name + end + + begin + opt = create_options + opt[:decode_typemap] = @rpc_decode_typemap + res_header, res_body = invoke(req_header, req_body, op_info, opt) + if res_body.fault + raise SOAP::FaultError.new(res_body.fault) + end + rescue SOAP::FaultError => e + Mapping.fault2exception(e) + end + + ret = res_body.response ? + Mapping.soap2obj(res_body.response, @mapping_registry) : nil + + if res_body.outparams + outparams = res_body.outparams.collect { |outparam| + Mapping.soap2obj(outparam) + } + return [ret].concat(outparams) + else + return ret + end + end + + # req_header: [[element, mustunderstand, encodingstyle(QName/String)], ...] + # req_body: SOAPBasetype/SOAPCompoundtype + def document_send(name, header_obj, body_obj) + log(INFO) { "document_send: sending document '#{ name }'." } + op_info = @operations[name] + req_header = header_from_obj(header_obj, op_info) + req_body = body_from_obj(body_obj, op_info) + opt = create_options + res_header, res_body = invoke(req_header, req_body, op_info, opt) + if res_body.fault + raise SOAP::FaultError.new(res_body.fault) + end + res_body_obj = res_body.response ? + Mapping.soap2obj(res_body.response, @mapping_registry) : nil + return res_header, res_body_obj + end + + private + + def create_handler + endpoint_url = @endpoint_url || @port.soap_address.location + @handler = HTTPPostStreamHandler.new(endpoint_url, @httpproxy, + XSD::Charset.encoding_label) + @handler.wiredump_dev = @wiredump_dev + end + + def create_method_obj(names, params) + o = Object.new + for idx in 0 ... params.length + o.instance_eval("@#{ names[idx] } = params[idx]") + end + o + end + + def invoke(req_header, req_body, op_info, opt) + send_string = Processor.marshal(req_header, req_body, opt) + log(DEBUG) { "invoke: sending string #{ send_string }" } + data = @handler.send(send_string, op_info.soapaction) + log(DEBUG) { "invoke: received string #{ data.receive_string }" } + if data.receive_string.empty? + return nil, nil + end + res_charset = StreamHandler.parse_media_type(data.receive_contenttype) + opt[:charset] = res_charset + res_header, res_body = Processor.unmarshal(data.receive_string, opt) + return res_header, res_body + end + + def header_from_obj(obj, op_info) + if obj.is_a?(SOAPHeader) + obj + elsif op_info.headerparts.empty? + if obj.nil? + nil + else + raise RuntimeError.new("No header definition in schema.") + end + elsif op_info.headerparts.size == 1 + part = op_info.headerparts[0] + header = SOAPHeader.new() + header.add(headeritem_from_obj(obj, part.element || part.eletype)) + header + else + header = SOAPHeader.new() + op_info.headerparts.each do |part| + child = obj[part.elename.name] + ele = headeritem_from_obj(child, part.element || part.eletype) + header.add(ele) + end + header + end + end + + def headeritem_from_obj(obj, name) + if obj.nil? + SOAPElement.new(name) + elsif obj.is_a?(SOAPHeaderItem) + obj + else + @doc_mapper.obj2ele(obj, name) + end + end + + def body_from_obj(obj, op_info) + if obj.is_a?(SOAPBody) + obj + elsif op_info.bodyparts.empty? + if obj.nil? + nil + else + raise RuntimeError.new("No body found in schema.") + end + elsif op_info.bodyparts.size == 1 + part = op_info.bodyparts[0] + ele = bodyitem_from_obj(obj, part.element || part.type) + SOAPBody.new(ele) + else + body = SOAPBody.new + op_info.bodyparts.each do |part| + child = obj[part.elename.name] + ele = bodyitem_from_obj(child, part.element || part.type) + body.add(ele.elename.name, ele) + end + body + end + end + + def bodyitem_from_obj(obj, name) + if obj.nil? + SOAPElement.new(name) + elsif obj.is_a?(SOAPElement) + obj + else + @doc_mapper.obj2ele(obj, name) + end + end + + def add_method_interface(op_info) + case op_info.style + when :document + add_document_method_interface(op_info.op_name.name) + when :rpc + parts_names = op_info.bodyparts.collect { |part| part.name } + add_rpc_method_interface(op_info.op_name.name, parts_names) + else + raise RuntimeError.new("Unknown style: #{op_info.style}") + end + end + + def add_document_method_interface(name) + @host.instance_eval <<-EOS + def #{ name }(headers, body) + @servant.document_send(#{ name.dump }, headers, body) + end + EOS + end + + def add_rpc_method_interface(name, parts_names) + i = 0 + param_names = parts_names.collect { |orgname| i += 1; "arg#{ i }" } + callparam_str = (param_names.collect { |pname| ", " + pname }).join + @host.instance_eval <<-EOS + def #{ name }(#{ param_names.join(", ") }) + @servant.rpc_send(#{ name.dump }#{ callparam_str }) + end + EOS + end + + def create_options + opt = @opt.dup + opt[:default_encodingstyle] = @default_encodingstyle + opt[:allow_unqualified_element] = @allow_unqualified_element + opt[:generate_explicit_type] = @generate_explicit_type + opt + end + + def log(sev) + @logdev.add(sev, nil, self.class) { yield } if @logdev + end + end + + def initialize(wsdl, port, logdev, opt) + @servant = Servant__.new(self, wsdl, port, logdev, opt) + end +end + + +end + + |