=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 name = 'http_proxy' @httpproxy = ENV[name] || ENV[name.upcase] @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