diff options
author | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2005-05-22 13:03:38 +0000 |
---|---|---|
committer | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2005-05-22 13:03:38 +0000 |
commit | eb3f829be932514064a983eb98fa7f840137f985 (patch) | |
tree | c17440206af25b52f47b1c391f818e126a994d0a /lib/soap | |
parent | 7aea792d3bdf42c349170e710953d7de29de57ab (diff) |
* lib/{soap,wsdl,xsd}, test/{soap,wsdl,xsd}: imported soap4r/1.5.4.
== SOAP client and server ==
=== for both client side and server side ===
* improved document/literal service support.
style(rpc,document)/use(encoding, literal) combination are all
supported. for the detail about combination, see
test/soap/test_style.rb.
* let WSDLEncodedRegistry#soap2obj map SOAP/OM to Ruby according to
WSDL as well as obj2soap. closes #70.
* let SOAP::Mapping::Object handle XML attribute for doc/lit service.
you can set/get XML attribute via accessor methods which as a name
'xmlattr_' prefixed (<foo name="bar"/> -> Foo#xmlattr_name).
=== client side ===
* WSDLDriver capitalized name operation bug fixed. from
1.5.3-ruby1.8.2, operation which has capitalized name (such as
KeywordSearchRequest in AWS) is defined as a method having
uncapitalized name. (converted with GenSupport.safemethodname
to handle operation name 'foo-bar'). it introduced serious
incompatibility; in the past, it was defined as a capitalized.
define capitalized method as well under that circumstance.
* added new factory interface 'WSDLDriverFactory#create_rpc_driver'
to create RPC::Driver, not WSDLDriver (RPC::Driver and WSDLDriver
are merged). 'WSDLDriverFactory#create_driver' still creates
WSDLDriver for compatibility but it warns that the method is
deprecated. please use create_rpc_driver instead of create_driver.
* allow to use an URI object as an endpoint_url even with net/http,
not http-access2.
=== server side ===
* added mod_ruby support to SOAP::CGIStub. rename a CGI script
server.cgi to server.rb and let mod_ruby's RubyHandler handles the
script. CGIStub detects if it's running under mod_ruby environment
or not.
* added fcgi support to SOAP::CGIStub. see the sample at
sample/soap/calc/server.fcgi. (almost same as server.cgi but has
fcgi handler at the bottom.)
* allow to return a SOAPFault object to respond customized SOAP fault.
* added the interface 'generate_explicit_type' for server side
(CGIStub, HTTPServer). call 'self.generate_explicit_type = true'
if you want to return simplified XML even if it's rpc/encoded
service.
== WSDL ==
=== WSDL definition ===
* improved XML Schema support such as extension, restriction,
simpleType, complexType + simpleContent, ref, length, import,
include.
* reduced "unknown element/attribute" warnings (warn only 1 time for
each QName).
* importing XSD file at schemaLocation with xsd:import.
=== code generation from WSDL ===
* generator crashed when there's '-' in defined element/attribute
name.
* added ApacheMap WSDL definition.
* sample/{soap,wsdl}: removed.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@8500 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/soap')
27 files changed, 2182 insertions, 1331 deletions
diff --git a/lib/soap/baseData.rb b/lib/soap/baseData.rb index bf2fe6b25c..6d0f628be9 100644 --- a/lib/soap/baseData.rb +++ b/lib/soap/baseData.rb @@ -1,5 +1,5 @@ # soap/baseData.rb: SOAP4R - Base type library -# Copyright (C) 2000, 2001, 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2000, 2001, 2003-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -56,6 +56,14 @@ module SOAPType @extraattr = {} end + def inspect + if self.is_a?(XSD::NSDBase) + sprintf("#<%s:0x%x %s %s>", self.class.name, __id__, self.elename, self.type) + else + sprintf("#<%s:0x%x %s>", self.class.name, __id__, self.elename) + end + end + def rootnode node = self while node = node.parent @@ -399,7 +407,7 @@ public def to_s() str = '' self.each do |key, data| - str << "#{ key }: #{ data }\n" + str << "#{key}: #{data}\n" end str end @@ -442,6 +450,25 @@ public @array end + def to_obj + hash = {} + proptype = {} + each do |k, v| + value = v.respond_to?(:to_obj) ? v.to_obj : v.to_s + case proptype[k] + when :single + hash[k] = [hash[k], value] + proptype[k] = :multi + when :multi + hash[k] << value + else + hash[k] = value + proptype[k] = :single + end + end + hash + end + def each for i in 0..(@array.length - 1) yield(@array[i], @data[i]) @@ -509,6 +536,10 @@ class SOAPElement @text = text end + def inspect + sprintf("#<%s:0x%x %s>", self.class.name, __id__, self.elename) + end + # Text interface. attr_accessor :text alias data text @@ -548,8 +579,19 @@ class SOAPElement @text else hash = {} + proptype = {} each do |k, v| - hash[k] = v.is_a?(SOAPElement) ? v.to_obj : v.to_s + value = v.respond_to?(:to_obj) ? v.to_obj : v.to_s + case proptype[k] + when :single + hash[k] = [hash[k], value] + proptype[k] = :multi + when :multi + hash[k] << value + else + hash[k] = value + proptype[k] = :single + end end hash end @@ -566,20 +608,41 @@ class SOAPElement o end - def self.from_obj(hash_or_string) + def self.from_obj(obj, namespace = nil) o = SOAPElement.new(nil) - if hash_or_string.is_a?(Hash) - hash_or_string.each do |k, v| - child = self.from_obj(v) - child.elename = k.is_a?(XSD::QName) ? k : XSD::QName.new(nil, k.to_s) - o.add(child) + case obj + when nil + o.text = nil + when Hash + obj.each do |elename, value| + if value.is_a?(Array) + value.each do |subvalue| + child = from_obj(subvalue, namespace) + child.elename = to_elename(elename, namespace) + o.add(child) + end + else + child = from_obj(value, namespace) + child.elename = to_elename(elename, namespace) + o.add(child) + end end else - o.text = hash_or_string + o.text = obj.to_s end o end + def self.to_elename(obj, namespace = nil) + if obj.is_a?(XSD::QName) + obj + elsif /\A(.+):([^:]+)\z/ =~ obj.to_s + XSD::QName.new($1, $2) + else + XSD::QName.new(namespace, obj.to_s) + end + end + private def add_member(name, value) @@ -590,18 +653,32 @@ private value end - def add_accessor(name) - methodname = name - if self.respond_to?(methodname) - methodname = safe_accessor_name(methodname) + if RUBY_VERSION > "1.7.0" + def add_accessor(name) + methodname = name + if self.respond_to?(methodname) + methodname = safe_accessor_name(methodname) + end + Mapping.define_singleton_method(self, methodname) do + @data[@array.index(name)] + end + Mapping.define_singleton_method(self, methodname + '=') do |value| + @data[@array.index(name)] = value + end + end + else + def add_accessor(name) + methodname = safe_accessor_name(name) + instance_eval <<-EOS + def #{methodname} + @data[@array.index(#{name.dump})] + end + + def #{methodname}=(value) + @data[@array.index(#{name.dump})] = value + end + EOS end - sclass = class << self; self; end - sclass.__send__(:define_method, methodname, proc { - @data[@array.index(name)] - }) - sclass.__send__(:define_method, methodname + '=', proc { |value| - @data[@array.index(name)] = value - }) end def safe_accessor_name(name) @@ -624,7 +701,7 @@ public def initialize(type = nil, rank = 1, arytype = nil) super() - @type = type || XSD::QName.new + @type = type || ValueArrayName @rank = rank @data = Array.new @sparse = false @@ -646,7 +723,7 @@ public def [](*idxary) if idxary.size != @rank - raise ArgumentError.new("Given #{ idxary.size } params does not match rank: #{ @rank }") + raise ArgumentError.new("given #{idxary.size} params does not match rank: #{@rank}") end retrieve(idxary) @@ -656,7 +733,8 @@ public value = idxary.slice!(-1) if idxary.size != @rank - raise ArgumentError.new("Given #{ idxary.size } params(#{ idxary }) does not match rank: #{ @rank }") + raise ArgumentError.new("given #{idxary.size} params(#{idxary})" + + " does not match rank: #{@rank}") end for i in 0..(idxary.size - 1) @@ -836,7 +914,7 @@ public private def self.create_arytype(typename, rank) - "#{ typename }[" << ',' * (rank - 1) << ']' + "#{typename}[" << ',' * (rank - 1) << ']' end TypeParseRegexp = Regexp.new('^(.+)\[([\d,]*)\]$') diff --git a/lib/soap/element.rb b/lib/soap/element.rb index 1494cd61dd..20d7c3bb2c 100644 --- a/lib/soap/element.rb +++ b/lib/soap/element.rb @@ -1,5 +1,5 @@ # SOAP4R - SOAP elements library -# Copyright (C) 2000, 2001, 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2000, 2001, 2003-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -99,7 +99,15 @@ class SOAPBody < SOAPStruct super(nil) @elename = EleBodyName @encodingstyle = nil - add(data.elename.name, data) if data + if data + if data.respond_to?(:elename) + add(data.elename.name, data) + else + data.to_a.each do |datum| + add(datum.elename.name, datum) + end + end + end @is_fault = is_fault end @@ -129,7 +137,7 @@ class SOAPBody < SOAPStruct end end - raise SOAPParser::FormatDecodeError.new('No root element.') + raise Parser::FormatDecodeError.new('no root element') end end @@ -231,8 +239,7 @@ class SOAPEnvelope < XSD::NSDBase end def encode(generator, ns, attrs = {}) - SOAPGenerator.assign_ns(attrs, ns, EnvelopeNamespace, - SOAPNamespaceTag) + SOAPGenerator.assign_ns(attrs, ns, EnvelopeNamespace, SOAPNamespaceTag) name = ns.name(@elename) generator.encode_tag(name, attrs) diff --git a/lib/soap/encodingstyle/aspDotNetHandler.rb b/lib/soap/encodingstyle/aspDotNetHandler.rb index e976433a7b..e4b2028f89 100644 --- a/lib/soap/encodingstyle/aspDotNetHandler.rb +++ b/lib/soap/encodingstyle/aspDotNetHandler.rb @@ -1,5 +1,5 @@ # SOAP4R - ASP.NET EncodingStyle handler library -# Copyright (C) 2001, 2003 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2001, 2003, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -30,7 +30,7 @@ class ASPDotNetHandler < Handler def encode_data(generator, ns, qualified, data, parent) attrs = {} name = if qualified and data.elename.namespace - SOAPGenerator.assign_ns(attrs, ns, data.elename.namespace) + SOAPGenerator.assign_ns(attrs, ns, data.elename.namespace, '') ns.name(data.elename) else data.elename.name @@ -61,8 +61,8 @@ class ASPDotNetHandler < Handler yield(child, true) end else - raise EncodingStyleError.new("Unknown object:#{ data } in this encodingSt -yle.") + raise EncodingStyleError.new( + "unknown object:#{data} in this encodingStyle") end end @@ -119,7 +119,6 @@ yle.") end def decode_tag(ns, elename, attrs, parent) - # ToDo: check if @textbuf is empty... @textbuf = '' o = SOAPUnknown.new(self, elename) o.parent = parent @@ -190,11 +189,11 @@ yle.") end when SOAPBasetype - raise EncodingStyleError.new("SOAP base type must not have a child.") + raise EncodingStyleError.new("SOAP base type must not have a child") else # SOAPUnknown does not have parent. - # raise EncodingStyleError.new("Illegal parent: #{ parent }.") + # raise EncodingStyleError.new("illegal parent: #{parent}") end end diff --git a/lib/soap/encodingstyle/literalHandler.rb b/lib/soap/encodingstyle/literalHandler.rb index 5b702785e6..9a494f9295 100644 --- a/lib/soap/encodingstyle/literalHandler.rb +++ b/lib/soap/encodingstyle/literalHandler.rb @@ -1,5 +1,5 @@ # SOAP4R - XML Literal EncodingStyle handler library -# Copyright (C) 2001, 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2001, 2003-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -29,11 +29,22 @@ class LiteralHandler < Handler def encode_data(generator, ns, qualified, data, parent) attrs = {} name = if qualified and data.elename.namespace - SOAPGenerator.assign_ns(attrs, ns, data.elename.namespace) + SOAPGenerator.assign_ns(attrs, ns, data.elename.namespace, '') ns.name(data.elename) else data.elename.name end + data.extraattr.each do |k, v| + if k.is_a?(XSD::QName) + if k.namespace + SOAPGenerator.assign_ns(attrs, ns, k.namespace) + k = ns.name(k) + else + k = k.name + end + end + attrs[k] = v + end case data when SOAPRawString @@ -62,13 +73,14 @@ class LiteralHandler < Handler yield(child, true) end when SOAPElement - generator.encode_tag(name, attrs.update(data.extraattr)) + generator.encode_tag(name, attrs) generator.encode_rawstring(data.text) if data.text data.each do |key, value| yield(value, qualified) end else - raise EncodingStyleError.new("Unknown object:#{ data } in this encodingStyle.") + raise EncodingStyleError.new( + "unknown object:#{data} in this encodingStyle") end end @@ -78,7 +90,8 @@ class LiteralHandler < Handler else data.elename.name end - cr = data.is_a?(SOAPElement) && !data.text + cr = (data.is_a?(SOAPCompoundtype) or + (data.is_a?(SOAPElement) and !data.text)) generator.encode_tag_end(name, cr) end @@ -128,7 +141,6 @@ class LiteralHandler < Handler end def decode_tag(ns, elename, attrs, parent) - # ToDo: check if @textbuf is empty... @textbuf = '' o = SOAPUnknown.new(self, elename, decode_attrs(ns, attrs)) o.parent = parent @@ -172,21 +184,19 @@ class LiteralHandler < Handler end def decode_parent(parent, node) + return unless parent.node case parent.node when SOAPUnknown newparent = parent.node.as_element node.parent = newparent parent.replace_node(newparent) decode_parent(parent, node) - when SOAPElement parent.node.add(node) node.parent = parent.node - when SOAPStruct parent.node.add(node.elename.name, node) node.parent = parent.node - when SOAPArray if node.position parent.node[*(decode_arypos(node.position))] = node @@ -195,13 +205,8 @@ class LiteralHandler < Handler parent.node.add(node) end node.parent = parent.node - - when SOAPBasetype - raise EncodingStyleError.new("SOAP base type must not have a child.") - else - # SOAPUnknown does not have parent. - raise EncodingStyleError.new("Illegal parent: #{ parent }.") + raise EncodingStyleError.new("illegal parent: #{parent.node}") end end diff --git a/lib/soap/encodingstyle/soapHandler.rb b/lib/soap/encodingstyle/soapHandler.rb index 134a2ae8b8..f9152e0df8 100644 --- a/lib/soap/encodingstyle/soapHandler.rb +++ b/lib/soap/encodingstyle/soapHandler.rb @@ -1,5 +1,5 @@ # SOAP4R - SOAP EncodingStyle handler library -# Copyright (C) 2001, 2003 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2001, 2003, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -33,7 +33,7 @@ class SOAPHandler < Handler attrs = encode_attrs(generator, ns, data, parent) if parent && parent.is_a?(SOAPArray) && parent.position - attrs[ns.name(AttrPositionName)] = "[#{ parent.position.join(',') }]" + attrs[ns.name(AttrPositionName)] = "[#{parent.position.join(',')}]" end name = nil @@ -75,7 +75,7 @@ class SOAPHandler < Handler end else raise EncodingStyleError.new( - "Unknown object:#{ data } in this encodingStyle.") + "unknown object:#{data} in this encodingStyle") end end @@ -156,7 +156,6 @@ class SOAPHandler < Handler end def decode_tag(ns, elename, attrs, parent) - # ToDo: check if @textbuf is empty... @textbuf = '' is_nil, type, arytype, root, offset, position, href, id, extraattr = decode_attrs(ns, attrs) @@ -227,6 +226,7 @@ class SOAPHandler < Handler end def decode_parent(parent, node) + return unless parent.node case parent.node when SOAPUnknown newparent = parent.node.as_struct @@ -247,10 +247,8 @@ class SOAPHandler < Handler parent.node.add(node) end node.parent = parent.node - when SOAPBasetype - raise EncodingStyleError.new("SOAP base type must not have a child.") else - raise EncodingStyleError.new("Illegal parent: #{ parent.node }.") + raise EncodingStyleError.new("illegal parent: #{parent.node}") end end @@ -266,7 +264,7 @@ private def create_arytype(ns, data) XSD::QName.new(data.arytype.namespace, - content_typename(data.arytype.name) + "[#{ data.size.join(',') }]") + content_typename(data.arytype.name) + "[#{data.size.join(',')}]") end def encode_attrs(generator, ns, data, parent) @@ -383,7 +381,7 @@ private def decode_definedtype(elename, typename, typedef, arytypestr) unless typedef - raise EncodingStyleError.new("Unknown type '#{ typename }'.") + raise EncodingStyleError.new("unknown type '#{typename}'") end if typedef.is_a?(::WSDL::XMLSchema::SimpleType) decode_defined_simpletype(elename, typename, typedef, arytypestr) @@ -421,6 +419,10 @@ private end o.definedtype = typedef return o + when :TYPE_EMPTY + o = SOAPNil.decode(elename) + o.definedtype = typedef + return o else raise RuntimeError.new( "Unknown kind of complexType: #{typedef.compoundtype}") @@ -509,7 +511,7 @@ private case qname.name when XSD::NilLiteral is_nil = NilLiteralMap[value] or - raise EncodingStyleError.new("Cannot accept attribute value: #{ value } as the value of xsi:#{ XSD::NilLiteral } (expected 'true', 'false', '1', or '0').") + raise EncodingStyleError.new("cannot accept attribute value: #{value} as the value of xsi:#{XSD::NilLiteral} (expected 'true', 'false', '1', or '0')") next when XSD::AttrType type = value @@ -523,7 +525,7 @@ private when AttrRoot root = RootLiteralMap[value] or raise EncodingStyleError.new( - "Illegal root attribute value: #{ value }.") + "illegal root attribute value: #{value}") next when AttrOffset offset = value @@ -578,7 +580,7 @@ private ref.__setobj__(o) false else - raise EncodingStyleError.new("Unresolved reference: #{ ref.refid }.") + raise EncodingStyleError.new("unresolved reference: #{ref.refid}") end } count -= 1 diff --git a/lib/soap/generator.rb b/lib/soap/generator.rb index edd90492c6..a44d1468b1 100644 --- a/lib/soap/generator.rb +++ b/lib/soap/generator.rb @@ -175,7 +175,12 @@ public def self.assign_ns(attrs, ns, namespace, tag = nil) if namespace and !ns.assigned?(namespace) tag = ns.assign(namespace, tag) - attrs['xmlns:' << tag] = namespace + if tag == '' + attr = 'xmlns' + else + attr = "xmlns:#{tag}" + end + attrs[attr] = namespace end end diff --git a/lib/soap/header/handlerset.rb b/lib/soap/header/handlerset.rb index 499d6bb8a1..a8eee03023 100644 --- a/lib/soap/header/handlerset.rb +++ b/lib/soap/header/handlerset.rb @@ -18,6 +18,12 @@ class HandlerSet @store = XSD::NamedElements.new end + def dup + obj = HandlerSet.new + obj.store = @store.dup + obj + end + def add(handler) @store << handler end @@ -51,6 +57,12 @@ class HandlerSet end end end + +protected + + def store=(store) + @store = store + end end diff --git a/lib/soap/header/simplehandler.rb b/lib/soap/header/simplehandler.rb index e7268e04a3..4c6b00ab96 100644 --- a/lib/soap/header/simplehandler.rb +++ b/lib/soap/header/simplehandler.rb @@ -1,5 +1,5 @@ # SOAP4R - SOAP Simple header item handler -# Copyright (C) 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2003-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -30,7 +30,7 @@ class SimpleHandler < SOAP::Header::Handler def on_outbound h = on_simple_outbound - h ? SOAPElement.from_obj(h) : nil + h ? SOAPElement.from_obj(h, elename.namespace) : nil end def on_inbound(header, mustunderstand) diff --git a/lib/soap/httpconfigloader.rb b/lib/soap/httpconfigloader.rb new file mode 100644 index 0000000000..ebc17d8f99 --- /dev/null +++ b/lib/soap/httpconfigloader.rb @@ -0,0 +1,116 @@ +# SOAP4R - HTTP config loader. +# Copyright (C) 2005 NAKAMURA, Hiroshi <[email protected]>. + +# This program is copyrighted free software by NAKAMURA, Hiroshi. You can +# redistribute it and/or modify it under the same terms of Ruby's license; +# either the dual license version in 2003, or any later version. + + +require 'soap/property' + + +module SOAP + + +module HTTPConfigLoader +module_function + + def set_options(client, options) + client.proxy = options["proxy"] + options.add_hook("proxy") do |key, value| + client.proxy = value + end + client.no_proxy = options["no_proxy"] + options.add_hook("no_proxy") do |key, value| + client.no_proxy = value + end + if client.respond_to?(:protocol_version=) + client.protocol_version = options["protocol_version"] + options.add_hook("protocol_version") do |key, value| + client.protocol_version = value + end + end + ssl_config = options["ssl_config"] ||= ::SOAP::Property.new + set_ssl_config(client, ssl_config) + ssl_config.add_hook(true) do |key, value| + set_ssl_config(client, ssl_config) + end + basic_auth = options["basic_auth"] ||= ::SOAP::Property.new + set_basic_auth(client, basic_auth) + basic_auth.add_hook do |key, value| + set_basic_auth(client, basic_auth) + end + options.add_hook("connect_timeout") do |key, value| + client.connect_timeout = value + end + options.add_hook("send_timeout") do |key, value| + client.send_timeout = value + end + options.add_hook("receive_timeout") do |key, value| + client.receive_timeout = value + end + end + + def set_basic_auth(client, basic_auth) + basic_auth.values.each do |url, userid, passwd| + client.set_basic_auth(url, userid, passwd) + end + end + + def set_ssl_config(client, ssl_config) + ssl_config.each do |key, value| + cfg = client.ssl_config + case key + when 'client_cert' + cfg.client_cert = cert_from_file(value) + when 'client_key' + cfg.client_key = key_from_file(value) + when 'client_ca' + cfg.client_ca = value + when 'ca_path' + cfg.set_trust_ca(value) + when 'ca_file' + cfg.set_trust_ca(value) + when 'crl' + cfg.set_crl(value) + when 'verify_mode' + cfg.verify_mode = ssl_config_int(value) + when 'verify_depth' + cfg.verify_depth = ssl_config_int(value) + when 'options' + cfg.options = value + when 'ciphers' + cfg.ciphers = value + when 'verify_callback' + cfg.verify_callback = value + when 'cert_store' + cfg.cert_store = value + else + raise ArgumentError.new("unknown ssl_config property #{key}") + end + end + end + + def ssl_config_int(value) + if value.nil? or value.to_s.empty? + nil + else + begin + Integer(value) + rescue ArgumentError + ::SOAP::Property::Util.const_from_name(value.to_s) + end + end + end + + def cert_from_file(filename) + OpenSSL::X509::Certificate.new(File.open(filename) { |f| f.read }) + end + + def key_from_file(filename) + OpenSSL::PKey::RSA.new(File.open(filename) { |f| f.read }) + end +end + + +end diff --git a/lib/soap/mapping/factory.rb b/lib/soap/mapping/factory.rb index a535458c5a..13dffc2dd0 100644 --- a/lib/soap/mapping/factory.rb +++ b/lib/soap/mapping/factory.rb @@ -37,24 +37,28 @@ class Factory end def setiv2soap(node, obj, map) - # should we sort instance_variables? - obj.instance_variables.each do |var| - name = var.sub(/^@/, '') - node.add(Mapping.name2elename(name), - Mapping._obj2soap(obj.instance_variable_get(var), map)) + if obj.class.class_variables.include?('@@schema_element') + obj.class.class_eval('@@schema_element').each do |name, info| + type, qname = info + if qname + elename = qname.name + else + elename = Mapping.name2elename(name) + end + node.add(elename, + Mapping._obj2soap(obj.instance_variable_get('@' + name), map)) + end + else + # should we sort instance_variables? + obj.instance_variables.each do |var| + name = var.sub(/^@/, '') + elename = Mapping.name2elename(name) + node.add(elename, + Mapping._obj2soap(obj.instance_variable_get(var), map)) + end end end - # It breaks Thread.current[:SOAPMarshalDataKey]. - def mark_marshalled_obj(obj, soap_obj) - Thread.current[:SOAPMarshalDataKey][obj.__id__] = soap_obj - end - - # It breaks Thread.current[:SOAPMarshalDataKey]. - def mark_unmarshalled_obj(node, obj) - Thread.current[:SOAPMarshalDataKey][node.id] = obj - end - private def setiv2ary(obj, node, map) @@ -68,7 +72,7 @@ private node.each do |name, value| vars[Mapping.elename2name(name)] = Mapping._soap2obj(value, map) end - Mapping.set_instance_vars(obj, vars) + Mapping.set_attributes(obj, vars) end end @@ -156,20 +160,14 @@ class DateTimeFactory_ < Factory end def soap2obj(obj_class, node, info, map) - obj = nil - if obj_class == Time - obj = node.to_time - if obj.nil? - # Is out of range as a Time - return false - end - elsif obj_class == Date - obj = node.data + if node.respond_to?(:to_obj) + obj = node.to_obj(obj_class) + return false if obj.nil? + mark_unmarshalled_obj(node, obj) + return true, obj else return false end - mark_unmarshalled_obj(node, obj) - return true, obj end end diff --git a/lib/soap/mapping/mapping.rb b/lib/soap/mapping/mapping.rb index e04e3fa50d..626df8c82f 100644 --- a/lib/soap/mapping/mapping.rb +++ b/lib/soap/mapping/mapping.rb @@ -1,5 +1,5 @@ # SOAP4R - Ruby type mapping utility. -# Copyright (C) 2000, 2001, 2003, 2004 NAKAMURA Hiroshi <[email protected]>. +# Copyright (C) 2000, 2001, 2003-2005 NAKAMURA Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -23,10 +23,12 @@ module Mapping # TraverseSupport breaks Thread.current[:SOAPMarshalDataKey]. module TraverseSupport def mark_marshalled_obj(obj, soap_obj) + raise if obj.nil? Thread.current[:SOAPMarshalDataKey][obj.__id__] = soap_obj end def mark_unmarshalled_obj(node, obj) + return if obj.nil? # node.id is not Object#id but SOAPReference#id Thread.current[:SOAPMarshalDataKey][node.id] = obj end @@ -41,10 +43,10 @@ module Mapping soap_obj end - def self.soap2obj(node, registry = nil) + def self.soap2obj(node, registry = nil, klass = nil) registry ||= Mapping::DefaultRegistry Thread.current[:SOAPMarshalDataKey] = {} - obj = _soap2obj(node, registry) + obj = _soap2obj(node, registry, klass) Thread.current[:SOAPMarshalDataKey] = nil obj end @@ -107,21 +109,21 @@ module Mapping elsif registry registry.obj2soap(obj, type) else - raise MappingError.new("No mapping registry given.") + raise MappingError.new("no mapping registry given") end end - def self._soap2obj(node, registry) + def self._soap2obj(node, registry, klass = nil) if node.is_a?(SOAPReference) target = node.__getobj__ # target.id is not Object#id but SOAPReference#id if referent = Thread.current[:SOAPMarshalDataKey][target.id] return referent else - return _soap2obj(target, registry) + return _soap2obj(target, registry, klass) end end - return registry.soap2obj(node) + return registry.soap2obj(node, klass) end if Object.respond_to?(:allocate) @@ -157,29 +159,6 @@ module Mapping end end - unless Object.respond_to?(:instance_variable_get) - class Object - def instance_variable_get(ivarname) - instance_eval(ivarname) - end - - def instance_variable_set(ivarname, value) - instance_eval("#{ivarname} = value") - end - end - end - - def self.set_instance_vars(obj, values) - values.each do |name, value| - setter = name + "=" - if obj.respond_to?(setter) - obj.__send__(setter, value) - else - obj.instance_variable_set('@' + name, value) - end - end - end - # Allow only (Letter | '_') (Letter | Digit | '-' | '_')* here. # Caution: '.' is not allowed here. # To follow XML spec., it should be NCName. @@ -198,28 +177,51 @@ module Mapping } end - def self.class_from_name(name) - if /^[A-Z]/ !~ name + def self.const_from_name(name, lenient = false) + const = ::Object + name.sub(/\A::/, '').split('::').each do |const_str| + if XSD::CodeGen::GenSupport.safeconstname?(const_str) + if const.const_defined?(const_str) + const = const.const_get(const_str) + next + end + elsif lenient + const_str = XSD::CodeGen::GenSupport.safeconstname(const_str) + if const.const_defined?(const_str) + const = const.const_get(const_str) + next + end + end return nil end - klass = ::Object - name.split('::').each do |klass_str| - if klass.const_defined?(klass_str) - klass = klass.const_get(klass_str) - else - return nil - end + const + end + + def self.class_from_name(name, lenient = false) + const = const_from_name(name, lenient) + if const.is_a?(::Class) + const + else + nil + end + end + + def self.module_from_name(name, lenient = false) + const = const_from_name(name, lenient) + if const.is_a?(::Module) + const + else + nil end - klass end def self.class2qname(klass) - name = if klass.class_variables.include?("@@schema_type") + name = if klass.class_variables.include?('@@schema_type') klass.class_eval('@@schema_type') else nil end - namespace = if klass.class_variables.include?("@@schema_ns") + namespace = if klass.class_variables.include?('@@schema_ns') klass.class_eval('@@schema_ns') else nil @@ -250,19 +252,75 @@ module Mapping end end - def self.find_attribute(obj, attr_name) + def self.define_singleton_method(obj, name, &block) + sclass = (class << obj; self; end) + sclass.__send__(:define_method, name, &block) + end + + def self.get_attribute(obj, attr_name) if obj.is_a?(::Hash) obj[attr_name] || obj[attr_name.intern] else - name = ::XSD::CodeGen::GenSupport.safevarname(attr_name) - if obj.respond_to?(name) - obj.__send__(name) - else + name = XSD::CodeGen::GenSupport.safevarname(attr_name) + if obj.instance_variables.include?('@' + name) obj.instance_variable_get('@' + name) + elsif ((obj.is_a?(::Struct) or obj.is_a?(Marshallable)) and + obj.respond_to?(name)) + obj.__send__(name) + end + end + end + + def self.set_attributes(obj, values) + if obj.is_a?(::SOAP::Mapping::Object) + values.each do |attr_name, value| + obj.__add_xmlele_value(attr_name, value) + end + else + values.each do |attr_name, value| + name = XSD::CodeGen::GenSupport.safevarname(attr_name) + setter = name + "=" + if obj.respond_to?(setter) + obj.__send__(setter, value) + else + obj.instance_variable_set('@' + name, value) + begin + define_attr_accessor(obj, name, + proc { instance_variable_get('@' + name) }, + proc { |value| instance_variable_set('@' + name, value) }) + rescue TypeError + # singleton class may not exist (e.g. Float) + end + end end end end + def self.define_attr_accessor(obj, name, getterproc, setterproc = nil) + define_singleton_method(obj, name, &getterproc) + define_singleton_method(obj, name + '=', &setterproc) if setterproc + end + + def self.schema_element_definition(klass) + return nil unless klass.class_variables.include?('@@schema_element') + elements = {} + as_array = [] + klass.class_eval('@@schema_element').each do |varname, definition| + class_name, name = definition + if /\[\]$/ =~ class_name + class_name = class_name.sub(/\[\]$/, '') + as_array << class_name + end + elements[name ? name.name : varname] = class_name + end + [elements, as_array] + end + + def self.schema_attribute_definition(klass) + return nil unless klass.class_variables.include?('@@schema_attribute') + klass.class_eval('@@schema_attribute') + end + class << Mapping private def add_md_ary(md_ary, ary, indices, registry) diff --git a/lib/soap/mapping/registry.rb b/lib/soap/mapping/registry.rb index e62706f2d8..e733f5c95f 100644 --- a/lib/soap/mapping/registry.rb +++ b/lib/soap/mapping/registry.rb @@ -64,50 +64,105 @@ end # For anyType object: SOAP::Mapping::Object not ::Object class Object; include Marshallable def initialize - @__soap_value_type = {} - @__soap_value = {} + @__xmlele_type = {} + @__xmlele = [] + @__xmlattr = {} end - def [](name) - @__soap_value[name] + def inspect + sprintf("#<%s:0x%x%s>", self.class.name, __id__, + @__xmlele.collect { |name, value| " #{name}=#{value.inspect}" }.join) end - def []=(name, value) - @__soap_value[name] = value + def __xmlattr + @__xmlattr end - def __soap_set_property(name, value) - unless @__soap_value.key?(name) - __define_attr_accessor(name) + def __xmlele + @__xmlele + end + + def [](qname) + unless qname.is_a?(XSD::QName) + qname = XSD::QName.new(nil, qname) + end + @__xmlele.each do |k, v| + return v if k == qname + end + nil + end + + def []=(qname, value) + unless qname.is_a?(XSD::QName) + qname = XSD::QName.new(nil, qname) + end + found = false + @__xmlele.each do |pair| + if pair[0] == qname + found = true + pair[1] = value + end + end + unless found + __define_attr_accessor(qname) + @__xmlele << [qname, value] + end + @__xmlele_type[qname] = :single + end + + def __add_xmlele_value(qname, value) + found = false + @__xmlele.map! do |k, v| + if k == qname + found = true + [k, __set_xmlele_value(k, v, value)] + else + [k, v] + end + end + unless found + __define_attr_accessor(qname) + @__xmlele << [qname, value] + @__xmlele_type[qname] = :single end - __soap_set_property_value(name, value) + value end private - def __soap_set_property_value(name, value) - org = self[name] - case @__soap_value_type[name] - when :single - self[name] = [org, value] - @__soap_value_type[name] = :multi + if RUBY_VERSION > "1.7.0" + def __define_attr_accessor(qname) + name = XSD::CodeGen::GenSupport.safemethodname(qname.name) + Mapping.define_attr_accessor(self, name, + proc { self[qname] }, + proc { |value| self[qname] = value }) + end + else + def __define_attr_accessor(qname) + name = XSD::CodeGen::GenSupport.safemethodname(qname.name) + instance_eval <<-EOS + def #{name} + self[#{qname.dump}] + end + + def #{name}=(value) + self[#{qname.dump}] = value + end + EOS + end + end + + def __set_xmlele_value(key, org, value) + case @__xmlele_type[key] when :multi org << value + org + when :single + @__xmlele_type[key] = :multi + [org, value] else - self[name] = value - @__soap_value_type[name] = :single + raise RuntimeError.new("unknown type") end - value - end - - def __define_attr_accessor(name) - sclass = class << self; self; end - sclass.__send__(:define_method, name, proc { - self[name] - }) - sclass.__send__(:define_method, name + '=', proc { |value| - self[name] = value - }) end end @@ -123,7 +178,7 @@ class Registry @registry = registry end - def obj2soap(obj, type_qname = nil) + def obj2soap(obj) klass = obj.class if map = @obj2soap[klass] map.each do |soap_class, factory, info| @@ -131,7 +186,10 @@ class Registry return ret if ret end end - ancestors = klass.ancestors[1..-3] # except itself, Object and Kernel + ancestors = klass.ancestors + ancestors.delete(klass) + ancestors.delete(::Object) + ancestors.delete(::Kernel) ancestors.each do |klass| if map = @obj2soap[klass] map.each do |soap_class, factory, info| @@ -145,10 +203,10 @@ class Registry nil end - def soap2obj(node) - klass = node.class - if map = @soap2obj[klass] + def soap2obj(node, klass = nil) + if map = @soap2obj[node.class] map.each do |obj_class, factory, info| + next if klass and obj_class != klass conv, obj = factory.soap2obj(obj_class, node, info, @registry) return true, obj if conv end @@ -177,11 +235,13 @@ class Registry end def find_mapped_soap_class(target_obj_class) - @obj2soap[target_obj_class][0] + map = @obj2soap[target_obj_class] + map.empty? ? nil : map[0][1] end def find_mapped_obj_class(target_soap_class) - @soap2obj[target_soap_class][0] + map = @soap2obj[target_soap_class] + map.empty? ? nil : map[0][0] end end @@ -202,7 +262,6 @@ class Registry [::FalseClass, ::SOAP::SOAPBoolean, BasetypeFactory], [::String, ::SOAP::SOAPString, StringFactory], [::DateTime, ::SOAP::SOAPDateTime, DateTimeFactory], - [::Date, ::SOAP::SOAPDateTime, DateTimeFactory], [::Date, ::SOAP::SOAPDate, DateTimeFactory], [::Time, ::SOAP::SOAPDateTime, DateTimeFactory], [::Time, ::SOAP::SOAPTime, DateTimeFactory], @@ -266,7 +325,6 @@ class Registry [::FalseClass, ::SOAP::SOAPBoolean, BasetypeFactory], [::String, ::SOAP::SOAPString, StringFactory], [::DateTime, ::SOAP::SOAPDateTime, DateTimeFactory], - [::Date, ::SOAP::SOAPDateTime, DateTimeFactory], [::Date, ::SOAP::SOAPDate, DateTimeFactory], [::Time, ::SOAP::SOAPDateTime, DateTimeFactory], [::Time, ::SOAP::SOAPTime, DateTimeFactory], @@ -354,17 +412,17 @@ class Registry end alias set add - # This mapping registry ignores type hint. + # general Registry ignores type_qname def obj2soap(obj, type_qname = nil) - soap = _obj2soap(obj, type_qname) + soap = _obj2soap(obj) if @allow_original_mapping addextend2soap(soap, obj) end soap end - def soap2obj(node) - obj = _soap2obj(node) + def soap2obj(node, klass = nil) + obj = _soap2obj(node, klass) if @allow_original_mapping addextend2obj(obj, node.extraattr[RubyExtendName]) addiv2obj(obj, node.extraattr[RubyIVarName]) @@ -382,7 +440,7 @@ class Registry private - def _obj2soap(obj, type_qname) + def _obj2soap(obj) ret = nil if obj.is_a?(SOAPStruct) or obj.is_a?(SOAPArray) obj.replace do |ele| @@ -393,7 +451,7 @@ private return obj end begin - ret = @map.obj2soap(obj, type_qname) || + ret = @map.obj2soap(obj) || @default_factory.obj2soap(nil, obj, nil, self) return ret if ret rescue MappingError @@ -408,12 +466,12 @@ private end # Might return nil as a mapping result. - def _soap2obj(node) + def _soap2obj(node, klass = nil) if node.extraattr.key?(RubyTypeName) conv, obj = @rubytype_factory.soap2obj(nil, node, nil, self) return obj if conv else - conv, obj = @map.soap2obj(node) + conv, obj = @map.soap2obj(node, klass) return obj if conv conv, obj = @default_factory.soap2obj(nil, node, nil, self) return obj if conv @@ -435,14 +493,14 @@ private attr.__getobj__.each do |name, value| vars[name] = Mapping._soap2obj(value, self) end - Mapping.set_instance_vars(obj, vars) + Mapping.set_attributes(obj, vars) end if RUBY_VERSION >= '1.8.0' def addextend2obj(obj, attr) return unless attr attr.split(/ /).reverse_each do |mstr| - obj.extend(Mapping.class_from_name(mstr)) + obj.extend(Mapping.module_from_name(mstr)) end end else @@ -450,8 +508,8 @@ private def addextend2obj(obj, attr) return unless attr attr.split(/ /).reverse_each do |mstr| - m = Mapping.class_from_name(mstr) - obj.extend(m) if m.class == Module + m = Mapping.module_from_name(mstr) + obj.extend(m) end end end diff --git a/lib/soap/mapping/rubytypeFactory.rb b/lib/soap/mapping/rubytypeFactory.rb index 6266148b55..61c21d8b20 100644 --- a/lib/soap/mapping/rubytypeFactory.rb +++ b/lib/soap/mapping/rubytypeFactory.rb @@ -1,5 +1,5 @@ # SOAP4R - Ruby type mapping factory. -# Copyright (C) 2000, 2001, 2002, 2003 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2000-2003, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -168,7 +168,7 @@ class RubytypeFactory < Factory return nil end if obj.to_s[0] == ?# - raise TypeError.new("can't dump anonymous class #{ obj }") + raise TypeError.new("can't dump anonymous class #{obj}") end param = SOAPStruct.new(TYPE_CLASS) mark_marshalled_obj(obj, param) @@ -179,7 +179,7 @@ class RubytypeFactory < Factory return nil end if obj.to_s[0] == ?# - raise TypeError.new("can't dump anonymous module #{ obj }") + raise TypeError.new("can't dump anonymous module #{obj}") end param = SOAPStruct.new(TYPE_MODULE) mark_marshalled_obj(obj, param) @@ -222,7 +222,12 @@ class RubytypeFactory < Factory when ::SOAP::Mapping::Object param = SOAPStruct.new(XSD::AnyTypeName) mark_marshalled_obj(obj, param) - addiv2soapattr(param, obj, map) + obj.__xmlele.each do |key, value| + param.add(key.name, Mapping._obj2soap(value, map)) + end + obj.__xmlattr.each do |key, value| + param.extraattr[key] = value + end when ::Exception typestr = Mapping.name2elename(obj.class.to_s) param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, typestr)) @@ -258,12 +263,12 @@ private def unknownobj2soap(soap_class, obj, info, map) if obj.class.name.empty? - raise TypeError.new("can't dump anonymous class #{ obj }") + raise TypeError.new("can't dump anonymous class #{obj}") end singleton_class = class << obj; self; end if !singleton_methods_true(obj).empty? or !singleton_class.instance_variables.empty? - raise TypeError.new("singleton can't be dumped #{ obj }") + raise TypeError.new("singleton can't be dumped #{obj}") end if !(singleton_class.ancestors - obj.class.ancestors).empty? typestr = Mapping.name2elename(obj.class.to_s) @@ -378,7 +383,11 @@ private obj = klass.new mark_unmarshalled_obj(node, obj) node.each do |name, value| - obj.__soap_set_property(name, Mapping._soap2obj(value, map)) + obj.__add_xmlele_value(XSD::QName.new(nil, name), + Mapping._soap2obj(value, map)) + end + unless node.extraattr.empty? + obj.instance_variable_set('@__xmlattr', node.extraattr) end return true, obj else @@ -387,7 +396,12 @@ private end def unknowntype2obj(node, info, map) - if node.is_a?(SOAPStruct) + case node + when SOAPBasetype + return true, node.data + when SOAPArray + return @array_factory.soap2obj(Array, node, info, map) + when SOAPStruct obj = unknownstruct2obj(node, info, map) return true, obj if obj if !@allow_untyped_struct @@ -406,6 +420,9 @@ private end typestr = Mapping.elename2name(node.type.name) klass = Mapping.class_from_name(typestr) + if klass.nil? and @allow_untyped_struct + klass = Mapping.class_from_name(typestr, true) # lenient + end if klass.nil? return nil end @@ -414,7 +431,13 @@ private end klass_type = Mapping.class2qname(klass) return nil unless node.type.match(klass_type) - obj = Mapping.create_empty_object(klass) + obj = nil + begin + obj = Mapping.create_empty_object(klass) + rescue + # type name "data" tries Data.new which raises TypeError + nil + end mark_unmarshalled_obj(node, obj) setiv2obj(obj, node, map) obj diff --git a/lib/soap/mapping/wsdlencodedregistry.rb b/lib/soap/mapping/wsdlencodedregistry.rb index 335106fba1..8d495668e9 100644 --- a/lib/soap/mapping/wsdlencodedregistry.rb +++ b/lib/soap/mapping/wsdlencodedregistry.rb @@ -1,11 +1,13 @@ # SOAP4R - WSDL encoded mapping registry. -# Copyright (C) 2000, 2001, 2002, 2003 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2000-2003, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; # either the dual license version in 2003, or any later version. +require 'xsd/qname' +require 'xsd/namedelements' require 'soap/baseData' require 'soap/mapping/mapping' require 'soap/mapping/typeMap' @@ -15,35 +17,33 @@ module SOAP module Mapping -class WSDLEncodedRegistry +class WSDLEncodedRegistry < Registry include TraverseSupport + attr_reader :definedelements attr_reader :definedtypes attr_accessor :excn_handler_obj2soap + attr_accessor :excn_handler_soap2obj - def initialize(definedtypes, config = {}) + def initialize(definedtypes = XSD::NamedElements::Empty) @definedtypes = definedtypes - @config = config + # @definedelements = definedelements needed? @excn_handler_obj2soap = nil + @excn_handler_soap2obj = nil # For mapping AnyType element. @rubytype_factory = RubytypeFactory.new( :allow_untyped_struct => true, :allow_original_mapping => true ) + @schema_element_cache = {} end - def obj2soap(obj, type_qname = nil) + def obj2soap(obj, qname = nil) soap_obj = nil - if obj.nil? - soap_obj = SOAPNil.new - elsif type_qname.nil? or type_qname == XSD::AnyTypeName - soap_obj = @rubytype_factory.obj2soap(nil, obj, nil, self) - elsif obj.is_a?(XSD::NSDBase) - soap_obj = soap2soap(obj, type_qname) - elsif type = @definedtypes[type_qname] - soap_obj = obj2type(obj, type) - elsif (type = TypeMap[type_qname]) - soap_obj = base2soap(obj, type) + if type = @definedtypes[qname] + soap_obj = obj2typesoap(obj, type) + else + soap_obj = any2soap(obj, qname) end return soap_obj if soap_obj if @excn_handler_obj2soap @@ -52,15 +52,46 @@ class WSDLEncodedRegistry } return soap_obj if soap_obj end - raise MappingError.new("Cannot map #{ obj.class.name } to SOAP/OM.") + if qname + raise MappingError.new("cannot map #{obj.class.name} as #{qname}") + else + raise MappingError.new("cannot map #{obj.class.name} to SOAP/OM") + end end - def soap2obj(node) - raise RuntimeError.new("#{ self } is for obj2soap only.") + # map anything for now: must refer WSDL while mapping. [ToDo] + def soap2obj(node, obj_class = nil) + begin + return any2obj(node, obj_class) + rescue MappingError + end + if @excn_handler_soap2obj + begin + return @excn_handler_soap2obj.call(node) { |yield_node| + Mapping._soap2obj(yield_node, self) + } + rescue Exception + end + end + raise MappingError.new("cannot map #{node.type.name} to Ruby object") end private + def any2soap(obj, qname) + if obj.nil? + SOAPNil.new + elsif qname.nil? or qname == XSD::AnyTypeName + @rubytype_factory.obj2soap(nil, obj, nil, self) + elsif obj.is_a?(XSD::NSDBase) + soap2soap(obj, qname) + elsif (type = TypeMap[qname]) + base2soap(obj, type) + else + nil + end + end + def soap2soap(obj, type_qname) if obj.is_a?(SOAPBasetype) obj @@ -82,25 +113,22 @@ private end end - def obj2type(obj, type) + def obj2typesoap(obj, type) if type.is_a?(::WSDL::XMLSchema::SimpleType) - simple2soap(obj, type) + simpleobj2soap(obj, type) else - complex2soap(obj, type) + complexobj2soap(obj, type) end end - def simple2soap(obj, type) - o = base2soap(obj, TypeMap[type.base]) - if type.restriction.enumeration.empty? - STDERR.puts("#{type.name}: simpleType which is not enum type not supported.") - return o - end + def simpleobj2soap(obj, type) type.check_lexical_format(obj) + return SOAPNil.new if obj.nil? # ToDo: check nillable. + o = base2soap(obj, TypeMap[type.base]) o end - def complex2soap(obj, type) + def complexobj2soap(obj, type) case type.compoundtype when :TYPE_STRUCT struct2soap(obj, type.name, type) @@ -108,8 +136,13 @@ private array2soap(obj, type.name, type) when :TYPE_MAP map2soap(obj, type.name, type) + when :TYPE_SIMPLE + simpleobj2soap(obj, type.simplecontent) + when :TYPE_EMPTY + raise MappingError.new("should be empty") unless obj.nil? + SOAPNil.new else - raise MappingError.new("Unknown compound type: #{ type.compoundtype }") + raise MappingError.new("unknown compound type: #{type.compoundtype}") end end @@ -126,18 +159,24 @@ private end def struct2soap(obj, type_qname, type) + return SOAPNil.new if obj.nil? # ToDo: check nillable. soap_obj = SOAPStruct.new(type_qname) - mark_marshalled_obj(obj, soap_obj) - elements2soap(obj, soap_obj, type.content.elements) + unless obj.nil? + mark_marshalled_obj(obj, soap_obj) + elements2soap(obj, soap_obj, type.content.elements) + end soap_obj end def array2soap(obj, type_qname, type) + return SOAPNil.new if obj.nil? # ToDo: check nillable. arytype = type.child_type soap_obj = SOAPArray.new(ValueArrayName, 1, arytype) - mark_marshalled_obj(obj, soap_obj) - obj.each do |item| - soap_obj.add(Mapping._obj2soap(item, self, arytype)) + unless obj.nil? + mark_marshalled_obj(obj, soap_obj) + obj.each do |item| + soap_obj.add(Mapping._obj2soap(item, self, arytype)) + end end soap_obj end @@ -145,16 +184,19 @@ private MapKeyName = XSD::QName.new(nil, "key") MapValueName = XSD::QName.new(nil, "value") def map2soap(obj, type_qname, type) + return SOAPNil.new if obj.nil? # ToDo: check nillable. keytype = type.child_type(MapKeyName) || XSD::AnyTypeName valuetype = type.child_type(MapValueName) || XSD::AnyTypeName soap_obj = SOAPStruct.new(MapQName) - mark_marshalled_obj(obj, soap_obj) - obj.each do |key, value| - elem = SOAPStruct.new - elem.add("key", Mapping._obj2soap(key, self, keytype)) - elem.add("value", Mapping._obj2soap(value, self, valuetype)) - # ApacheAxis allows only 'item' here. - soap_obj.add("item", elem) + unless obj.nil? + mark_marshalled_obj(obj, soap_obj) + obj.each do |key, value| + elem = SOAPStruct.new + elem.add("key", Mapping._obj2soap(key, self, keytype)) + elem.add("value", Mapping._obj2soap(value, self, valuetype)) + # ApacheAxis allows only 'item' here. + soap_obj.add("item", elem) + end end soap_obj end @@ -162,10 +204,64 @@ private def elements2soap(obj, soap_obj, elements) elements.each do |element| name = element.name.name - child_obj = obj.instance_variable_get('@' + name) - soap_obj.add(name, Mapping._obj2soap(child_obj, self, element.type)) + child_obj = Mapping.get_attribute(obj, name) + soap_obj.add(name, + Mapping._obj2soap(child_obj, self, element.type || element.name)) + end + end + + def any2obj(node, obj_class) + unless obj_class + typestr = XSD::CodeGen::GenSupport.safeconstname(node.elename.name) + obj_class = Mapping.class_from_name(typestr) + end + if obj_class and obj_class.class_variables.include?('@@schema_element') + soap2stubobj(node, obj_class) + else + Mapping._soap2obj(node, Mapping::DefaultRegistry, obj_class) end end + + def soap2stubobj(node, obj_class) + obj = Mapping.create_empty_object(obj_class) + unless node.is_a?(SOAPNil) + add_elements2stubobj(node, obj) + end + obj + end + + def add_elements2stubobj(node, obj) + elements, as_array = schema_element_definition(obj.class) + vars = {} + node.each do |name, value| + if class_name = elements[name] + if klass = Mapping.class_from_name(class_name) + # klass must be a SOAPBasetype or a class + if klass.ancestors.include?(::SOAP::SOAPBasetype) + if value.respond_to?(:data) + child = klass.new(value.data).data + else + child = klass.new(nil).data + end + else + child = Mapping._soap2obj(value, self, klass) + end + else + raise MappingError.new("unknown class: #{class_name}") + end + else # untyped element is treated as anyType. + child = Mapping._soap2obj(value, self) + end + vars[name] = child + end + Mapping.set_attributes(obj, vars) + end + + # it caches @@schema_element. this means that @@schema_element must not be + # changed while a lifetime of a WSDLLiteralRegistry. + def schema_element_definition(klass) + @schema_element_cache[klass] ||= Mapping.schema_element_definition(klass) + end end diff --git a/lib/soap/mapping/wsdlliteralregistry.rb b/lib/soap/mapping/wsdlliteralregistry.rb index c190c9d904..59acf2f658 100644 --- a/lib/soap/mapping/wsdlliteralregistry.rb +++ b/lib/soap/mapping/wsdlliteralregistry.rb @@ -1,5 +1,5 @@ # SOAP4R - WSDL literal mapping registry. -# Copyright (C) 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2004, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -10,48 +10,55 @@ require 'soap/baseData' require 'soap/mapping/mapping' require 'soap/mapping/typeMap' require 'xsd/codegen/gensupport' +require 'xsd/namedelements' module SOAP module Mapping -class WSDLLiteralRegistry +class WSDLLiteralRegistry < Registry attr_reader :definedelements attr_reader :definedtypes attr_accessor :excn_handler_obj2soap attr_accessor :excn_handler_soap2obj - def initialize(definedelements = nil, definedtypes = nil) - @definedelements = definedelements + def initialize(definedtypes = XSD::NamedElements::Empty, + definedelements = XSD::NamedElements::Empty) @definedtypes = definedtypes - @rubytype_factory = RubytypeFactory.new(:allow_original_mapping => false) + @definedelements = definedelements + @excn_handler_obj2soap = nil + @excn_handler_soap2obj = nil @schema_element_cache = {} + @schema_attribute_cache = {} end def obj2soap(obj, qname) - ret = nil - if [email protected]? && ele = @definedelements[qname] - ret = _obj2soap(obj, ele) - elsif [email protected]? && type = @definedtypes[qname] - ret = obj2type(obj, type) + soap_obj = nil + if ele = @definedelements[qname] + soap_obj = obj2elesoap(obj, ele) + elsif type = @definedtypes[qname] + soap_obj = obj2typesoap(obj, type) else - ret = unknownobj2soap(obj, qname) + soap_obj = any2soap(obj, qname) end - return ret if ret + return soap_obj if soap_obj if @excn_handler_obj2soap - ret = @excn_handler_obj2soap.call(obj) { |yield_obj| + soap_obj = @excn_handler_obj2soap.call(obj) { |yield_obj| Mapping._obj2soap(yield_obj, self) } - return ret if ret + return soap_obj if soap_obj end - raise MappingError.new("Cannot map #{ obj.class.name } to SOAP/OM.") + raise MappingError.new("cannot map #{obj.class.name} as #{qname}") end # node should be a SOAPElement - def soap2obj(node) + def soap2obj(node, obj_class = nil) + unless obj_class.nil? + raise MappingError.new("must not reach here") + end begin - return soapele2obj(node) + return any2obj(node) rescue MappingError end if @excn_handler_soap2obj @@ -62,108 +69,164 @@ class WSDLLiteralRegistry rescue Exception end end - raise MappingError.new("Cannot map #{ node.type.name } to Ruby object.") + raise MappingError.new("cannot map #{node.type.name} to Ruby object") end private - def _obj2soap(obj, ele) + def obj2elesoap(obj, ele) o = nil if ele.type if type = @definedtypes[ele.type] - o = obj2type(obj, type) + o = obj2typesoap(obj, type) elsif type = TypeMap[ele.type] o = base2soap(obj, type) else - raise MappingError.new("Cannot find type #{ele.type}.") + raise MappingError.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_ele| - o.add(_obj2soap(Mapping.find_attribute(obj, child_ele.name.name), - child_ele)) - end + o = obj2typesoap(obj, ele.local_complextype) + o.elename = ele.name + add_attributes2soap(obj, o) + elsif ele.local_simpletype + o = obj2typesoap(obj, ele.local_simpletype) + o.elename = ele.name else - raise MappingError.new('Illegal schema?') + raise MappingError.new('illegal schema?') end o end - def obj2type(obj, type) + def obj2typesoap(obj, type) if type.is_a?(::WSDL::XMLSchema::SimpleType) - simple2soap(obj, type) + simpleobj2soap(obj, type) else - complex2soap(obj, type) + complexobj2soap(obj, type) end end - def simple2soap(obj, type) - o = base2soap(obj, TypeMap[type.base]) - if type.restriction.enumeration.empty? - STDERR.puts( - "#{type.name}: simpleType which is not enum type not supported.") - return o - end + def simpleobj2soap(obj, type) type.check_lexical_format(obj) + return SOAPNil.new if obj.nil? # ToDo: check nillable. + o = base2soap(obj, TypeMap[type.base]) o end - def complex2soap(obj, type) + def complexobj2soap(obj, type) o = SOAPElement.new(type.name) type.each_element do |child_ele| - o.add(_obj2soap(Mapping.find_attribute(obj, child_ele.name.name), - child_ele)) + child = Mapping.get_attribute(obj, child_ele.name.name) + if child.nil? + if child_ele.nillable + # ToDo: test + # add empty element + o.add(obj2elesoap(nil)) + elsif Integer(child_ele.minoccurs) == 0 + # nothing to do + else + raise MappingError.new("nil not allowed: #{child_ele.name.name}") + end + elsif child_ele.map_as_array? + child.each do |item| + o.add(obj2elesoap(item, child_ele)) + end + else + o.add(obj2elesoap(child, child_ele)) + end end o end - def unknownobj2soap(obj, name) - if obj.class.class_variables.include?('@@schema_element') - ele = SOAPElement.new(name) - add_elements2soap(obj, ele) - add_attributes2soap(obj, ele) - ele + def any2soap(obj, qname) + if obj.is_a?(SOAPElement) + obj + elsif obj.class.class_variables.include?('@@schema_element') + stubobj2soap(obj, qname) + elsif obj.is_a?(SOAP::Mapping::Object) + mappingobj2soap(obj, qname) elsif obj.is_a?(Hash) ele = SOAPElement.from_obj(obj) - ele.elename = name + ele.elename = qname ele - else # expected to be a basetype or an anyType. - o = Mapping.obj2soap(obj) - o.elename = name - o + else + # expected to be a basetype or an anyType. + # SOAPStruct, etc. is used instead of SOAPElement. + begin + ele = Mapping.obj2soap(obj) + ele.elename = qname + ele + rescue MappingError + ele = SOAPElement.new(qname, obj.to_s) + end + if obj.respond_to?(:__xmlattr) + obj.__xmlattr.each do |key, value| + ele.extraattr[key] = value + end + end + ele + end + end + + def stubobj2soap(obj, qname) + ele = SOAPElement.new(qname) + add_elements2soap(obj, ele) + add_attributes2soap(obj, ele) + ele + end + + def mappingobj2soap(obj, qname) + ele = SOAPElement.new(qname) + obj.__xmlele.each do |key, value| + if value.is_a?(::Array) + value.each do |item| + ele.add(obj2soap(item, key)) + end + else + ele.add(obj2soap(value, key)) + end + end + obj.__xmlattr.each do |key, value| + ele.extraattr[key] = value end + ele end def add_elements2soap(obj, ele) elements, as_array = schema_element_definition(obj.class) - elements.each do |elename, type| - child = Mapping.find_attribute(obj, elename) - name = ::XSD::QName.new(nil, elename) - if as_array.include?(type) - child.each do |item| - ele.add(obj2soap(item, name)) + if elements + elements.each do |elename, type| + child = Mapping.get_attribute(obj, elename) + unless child.nil? + name = XSD::QName.new(nil, elename) + if as_array.include?(type) + child.each do |item| + ele.add(obj2soap(item, name)) + end + else + ele.add(obj2soap(child, name)) + end end - else - ele.add(obj2soap(child, name)) end end end def add_attributes2soap(obj, ele) attributes = schema_attribute_definition(obj.class) - attributes.each do |attrname, param| - attr = Mapping.find_attribute(obj, 'attr_' + attrname) - ele.extraattr[attrname] = attr + if attributes + attributes.each do |qname, param| + attr = obj.__send__('xmlattr_' + + XSD::CodeGen::GenSupport.safevarname(qname.name)) + ele.extraattr[qname] = attr + end end 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) + 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 @@ -176,35 +239,41 @@ private end klass = ::SOAP::Mapping::Object obj = klass.new - node.each do |name, value| - obj.__soap_set_property(name, Mapping.soap2obj(value)) - end obj end - def soapele2obj(node, obj_class = nil) + def any2obj(node, obj_class = nil) unless obj_class - typestr = ::XSD::CodeGen::GenSupport.safeconstname(node.elename.name) + typestr = XSD::CodeGen::GenSupport.safeconstname(node.elename.name) obj_class = Mapping.class_from_name(typestr) end if obj_class and obj_class.class_variables.include?('@@schema_element') - soapele2definedobj(node, obj_class) - elsif node.is_a?(SOAPElement) - node.to_obj + soapele2stubobj(node, obj_class) + elsif node.is_a?(SOAPElement) or node.is_a?(SOAPStruct) + # SOAPArray for literal? + soapele2plainobj(node) else - result, obj = @rubytype_factory.soap2obj(nil, node, nil, self) + obj = Mapping._soap2obj(node, Mapping::DefaultRegistry, obj_class) + add_attributes2plainobj(node, obj) obj end end - def soapele2definedobj(node, obj_class) + def soapele2stubobj(node, obj_class) obj = Mapping.create_empty_object(obj_class) - add_elements2obj(node, obj) - add_attributes2obj(node, obj) + add_elements2stubobj(node, obj) + add_attributes2stubobj(node, obj) obj end - def add_elements2obj(node, obj) + def soapele2plainobj(node) + obj = anytype2obj(node) + add_elements2plainobj(node, obj) + add_attributes2plainobj(node, obj) + obj + end + + def add_elements2stubobj(node, obj) elements, as_array = schema_element_definition(obj.class) vars = {} node.each do |name, value| @@ -217,13 +286,13 @@ private child = klass.new(nil).data end else - child = soapele2obj(value, klass) + child = any2obj(value, klass) end else - raise MappingError.new("Unknown class: #{class_name}") + raise MappingError.new("unknown class: #{class_name}") end else # untyped element is treated as anyType. - child = anytype2obj(value) + child = any2obj(value) end if as_array.include?(class_name) (vars[name] ||= []) << child @@ -231,48 +300,92 @@ private vars[name] = child end end - Mapping.set_instance_vars(obj, vars) + Mapping.set_attributes(obj, vars) end - def add_attributes2obj(node, obj) - Mapping.set_instance_vars(obj, {'__soap_attribute' => {}}) - vars = {} - attributes = schema_attribute_definition(obj.class) - attributes.each do |attrname, class_name| - attr = node.extraattr[::XSD::QName.new(nil, attrname)] - next if attr.nil? or attr.empty? - klass = Mapping.class_from_name(class_name) - if klass.ancestors.include?(::SOAP::SOAPBasetype) - child = klass.new(attr).data - else - child = attr + def add_attributes2stubobj(node, obj) + if attributes = schema_attribute_definition(obj.class) + define_xmlattr(obj) + attributes.each do |qname, class_name| + attr = node.extraattr[qname] + next if attr.nil? or attr.empty? + klass = Mapping.class_from_name(class_name) + if klass.ancestors.include?(::SOAP::SOAPBasetype) + child = klass.new(attr).data + else + child = attr + end + obj.__xmlattr[qname] = child + define_xmlattr_accessor(obj, qname) end - vars['attr_' + attrname] = child end - Mapping.set_instance_vars(obj, vars) end - # it caches @@schema_element. this means that @@schema_element must not be - # changed while a lifetime of a WSDLLiteralRegistry. - def schema_element_definition(klass) - if @schema_element_cache.key?(klass) - return @schema_element_cache[klass] + def add_elements2plainobj(node, obj) + node.each do |name, value| + obj.__add_xmlele_value(XSD::QName.new(nil, name), any2obj(value)) + end + end + + def add_attributes2plainobj(node, obj) + return if node.extraattr.empty? + define_xmlattr(obj) + node.extraattr.each do |qname, value| + obj.__xmlattr[qname] = value + define_xmlattr_accessor(obj, qname) + end + end + + if RUBY_VERSION > "1.7.0" + def define_xmlattr_accessor(obj, qname) + name = XSD::CodeGen::GenSupport.safemethodname(qname.name) + Mapping.define_attr_accessor(obj, 'xmlattr_' + name, + proc { @__xmlattr[qname] }, + proc { |value| @__xmlattr[qname] = value }) + end + else + def define_xmlattr_accessor(obj, qname) + name = XSD::CodeGen::GenSupport.safemethodname(qname.name) + obj.instance_eval <<-EOS + def #{name} + @__xmlattr[#{qname.dump}] + end + + def #{name}=(value) + @__xmlattr[#{qname.dump}] = value + end + EOS + end + end + + if RUBY_VERSION > "1.7.0" + def define_xmlattr(obj) + obj.instance_variable_set('@__xmlattr', {}) + unless obj.respond_to?(:__xmlattr) + Mapping.define_attr_accessor(obj, :__xmlattr, proc { @__xmlattr }) + end end - elements = {} - as_array = [] - klass.class_eval('@@schema_element').each do |name, class_name| - if /\[\]$/ =~ class_name - class_name = class_name.sub(/\[\]$/, '') - as_array << class_name + else + def define_xmlattr(obj) + obj.instance_variable_set('@__xmlattr', {}) + unless obj.respond_to?(:__xmlattr) + obj.instance_eval <<-EOS + def __xmlattr + @__xmlattr + end + EOS end - elements[name] = class_name end - @schema_element_cache[klass] = [elements, as_array] - return @schema_element_cache[klass] + end + + # it caches @@schema_element. this means that @@schema_element must not be + # changed while a lifetime of a WSDLLiteralRegistry. + def schema_element_definition(klass) + @schema_element_cache[klass] ||= Mapping.schema_element_definition(klass) end def schema_attribute_definition(klass) - attributes = klass.class_eval('@@schema_attribute') + @schema_attribute_cache[klass] ||= Mapping.schema_attribute_definition(klass) end end diff --git a/lib/soap/marshal.rb b/lib/soap/marshal.rb index 7202a6aba8..1c3d5b01db 100644 --- a/lib/soap/marshal.rb +++ b/lib/soap/marshal.rb @@ -14,8 +14,9 @@ module SOAP module Marshal - # Trying xsd:dateTime data to be recovered as aTime. aDateTime if it fails. - MarshalMappingRegistry = Mapping::Registry.new(:allow_original_mapping => true) + # Trying xsd:dateTime data to be recovered as aTime. + MarshalMappingRegistry = Mapping::Registry.new( + :allow_original_mapping => true) MarshalMappingRegistry.add( Time, ::SOAP::SOAPDateTime, diff --git a/lib/soap/netHttpClient.rb b/lib/soap/netHttpClient.rb index 5b16a5b443..10d68e2a4c 100644 --- a/lib/soap/netHttpClient.rb +++ b/lib/soap/netHttpClient.rb @@ -43,17 +43,22 @@ class NetHttpClient raise NotImplementedError.new("not supported for now") end - def proxy=(proxy_str) - if proxy_str.nil? + def proxy=(proxy) + if proxy.nil? @proxy = nil else - @proxy = URI.parse(proxy_str) + if proxy.is_a?(URI) + @proxy = proxy + else + @proxy = URI.parse(proxy) + end if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or @proxy.host == nil or @proxy.port == nil - raise ArgumentError.new("unsupported proxy `#{proxy_str}'") + raise ArgumentError.new("unsupported proxy `#{proxy}'") end - @proxy end + reset_all + @proxy end def set_basic_auth(uri, user_id, passwd) @@ -79,7 +84,9 @@ class NetHttpClient end def post(url, req_body, header = {}) - url = URI.parse(url) + unless url.is_a?(URI) + url = URI.parse(url) + end extra = header.dup extra['User-Agent'] = @agent if @agent res = start(url) { |http| @@ -89,7 +96,9 @@ class NetHttpClient end def get_content(url, header = {}) - url = URI.parse(url) + unless url.is_a?(URI) + url = URI.parse(url) + end extra = header.dup extra['User-Agent'] = @agent if @agent res = start(url) { |http| diff --git a/lib/soap/rpc/cgistub.rb b/lib/soap/rpc/cgistub.rb index e545d53c42..487f05a9bf 100644 --- a/lib/soap/rpc/cgistub.rb +++ b/lib/soap/rpc/cgistub.rb @@ -1,5 +1,5 @@ -# SOAP4R - CGI stub library -# Copyright (C) 2001, 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# SOAP4R - CGI/mod_ruby stub library +# Copyright (C) 2001, 2003-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -10,7 +10,7 @@ require 'soap/streamHandler' require 'webrick/httpresponse' require 'webrick/httpstatus' require 'logger' -require 'soap/rpc/router' +require 'soap/rpc/soaplet' module SOAP @@ -26,57 +26,51 @@ module RPC # class CGIStub < Logger::Application include SOAP + include WEBrick - # There is a client which does not accept the media-type which is defined in - # SOAP spec. - attr_accessor :mediatype + class SOAPRequest + attr_reader :body - class CGIError < Error; end + def [](var); end - class SOAPRequest - ALLOWED_LENGTH = 1024 * 1024 - - def initialize(stream = $stdin) - @method = ENV['REQUEST_METHOD'] - @size = ENV['CONTENT_LENGTH'].to_i || 0 - @contenttype = ENV['CONTENT_TYPE'] - @soapaction = ENV['HTTP_SOAPAction'] - @source = stream - @body = nil - end + def meta_vars; end + end - def init - validate - @body = @source.read(@size) - self - end + class SOAPStdinRequest < SOAPRequest + attr_reader :body - def dump - @body.dup + def initialize(stream) + size = ENV['CONTENT_LENGTH'].to_i || 0 + @body = stream.read(size) end - def soapaction - @soapaction + def [](var) + ENV[var.gsub(/-/, '_').upcase] end - def contenttype - @contenttype + def meta_vars + { + 'HTTP_SOAPACTION' => ENV['HTTP_SOAPAction'] + } end + end - def to_s - "method: #{ @method }, size: #{ @size }" - end + class SOAPFCGIRequest < SOAPRequest + attr_reader :body - private + def initialize(request) + @request = request + @body = @request.in.read + end - def validate # raise CGIError - if @method != 'POST' - raise CGIError.new("Method '#{ @method }' not allowed.") - end + def [](var) + @request.env[var.gsub(/-/, '_').upcase] + end - if @size > ALLOWED_LENGTH - raise CGIError.new("Content-length too long.") - end + def meta_vars + { + 'HTTP_SOAPACTION' => @request.env['HTTP_SOAPAction'] + } end end @@ -85,33 +79,14 @@ class CGIStub < Logger::Application set_log(STDERR) self.level = ERROR @default_namespace = default_namespace - @router = SOAP::RPC::Router.new(appname) - @remote_user = ENV['REMOTE_USER'] || 'anonymous' @remote_host = ENV['REMOTE_HOST'] || ENV['REMOTE_ADDR'] || 'unknown' - @request = nil - @response = nil - @mediatype = MediaType + @router = ::SOAP::RPC::Router.new(self.class.name) + @soaplet = ::SOAP::RPC::SOAPlet.new(@router) on_init end - def add_rpc_servant(obj, namespace = @default_namespace, soapaction = nil) - RPC.defined_methods(obj).each do |name| - qname = XSD::QName.new(namespace, name) - param_size = obj.method(name).arity.abs - params = (1..param_size).collect { |i| "p#{i}" } - param_def = SOAP::RPC::SOAPMethod.create_param_def(params) - @router.add_method(obj, qname, soapaction, name, param_def) - end - end - alias add_servant add_rpc_servant - - def add_rpc_headerhandler(obj) - @router.headerhandler << obj - end - alias add_headerhandler add_rpc_headerhandler - def on_init - # Override this method in derived class to call 'add_method' to add methods. + # do extra initialization in a derived class if needed. end def mapping_registry @@ -122,83 +97,108 @@ class CGIStub < Logger::Application @router.mapping_registry = value end - def add_method(receiver, name, *param) - add_method_with_namespace_as(@default_namespace, receiver, - name, name, *param) + def generate_explicit_type + @router.generate_explicit_type end - def add_method_as(receiver, name, name_as, *param) - add_method_with_namespace_as(@default_namespace, receiver, - name, name_as, *param) + def generate_explicit_type=(generate_explicit_type) + @router.generate_explicit_type = generate_explicit_type end - def add_method_with_namespace(namespace, receiver, name, *param) - add_method_with_namespace_as(namespace, receiver, name, name, *param) + # servant entry interface + + def add_rpc_servant(obj, namespace = @default_namespace) + @router.add_rpc_servant(obj, namespace) end + alias add_servant add_rpc_servant - def add_method_with_namespace_as(namespace, receiver, name, name_as, *param) - param_def = if param.size == 1 and param[0].is_a?(Array) - param[0] - else - SOAP::RPC::SOAPMethod.create_param_def(param) - end + def add_headerhandler(obj) + @router.add_headerhandler(obj) + end + alias add_rpc_headerhandler add_headerhandler + + # method entry interface + + def add_rpc_method(obj, name, *param) + add_rpc_method_with_namespace_as(@default_namespace, obj, name, name, *param) + end + alias add_method add_rpc_method + + def add_rpc_method_as(obj, name, name_as, *param) + add_rpc_method_with_namespace_as(@default_namespace, obj, name, name_as, *param) + end + alias add_method_as add_rpc_method_as + + def add_rpc_method_with_namespace(namespace, obj, name, *param) + add_rpc_method_with_namespace_as(namespace, obj, name, name, *param) + end + alias add_method_with_namespace add_rpc_method_with_namespace + + def add_rpc_method_with_namespace_as(namespace, obj, name, name_as, *param) qname = XSD::QName.new(namespace, name_as) - @router.add_method(receiver, qname, nil, name, param_def) + soapaction = nil + param_def = SOAPMethod.derive_rpc_param_def(obj, name, *param) + @router.add_rpc_operation(obj, qname, soapaction, name, param_def) end + alias add_method_with_namespace_as add_rpc_method_with_namespace_as - def route(conn_data) - @router.route(conn_data) + def add_rpc_operation(receiver, qname, soapaction, name, param_def, opt = {}) + @router.add_rpc_operation(receiver, qname, soapaction, name, param_def, opt) end - def create_fault_response(e) - @router.create_fault_response(e) + def add_document_operation(receiver, soapaction, name, param_def, opt = {}) + @router.add_document_operation(receiver, soapaction, name, param_def, opt) + end + + def set_fcgi_request(request) + @fcgi = request end private - - def run - prologue - httpversion = WEBrick::HTTPVersion.new('1.0') - @response = WEBrick::HTTPResponse.new({:HTTPVersion => httpversion}) - conn_data = nil + HTTPVersion = WEBrick::HTTPVersion.new('1.0') # dummy; ignored + + def run + res = WEBrick::HTTPResponse.new({:HTTPVersion => HTTPVersion}) begin - @log.info { "Received a request from '#{ @remote_user }@#{ @remote_host }'." } - # SOAP request parsing. - @request = SOAPRequest.new.init - @response['Status'] = 200 - conn_data = ::SOAP::StreamHandler::ConnectionData.new - conn_data.receive_string = @request.dump - conn_data.receive_contenttype = @request.contenttype - @log.debug { "XML Request: #{conn_data.receive_string}" } - conn_data = route(conn_data) - @log.debug { "XML Response: #{conn_data.send_string}" } - if conn_data.is_fault - @response['Status'] = 500 + @log.info { "received a request from '#{ @remote_host }'" } + if @fcgi + req = SOAPFCGIRequest.new(@fcgi) + else + req = SOAPStdinRequest.new($stdin) end - @response['Cache-Control'] = 'private' - @response.body = conn_data.send_string - @response['content-type'] = conn_data.send_contenttype - rescue Exception - conn_data = create_fault_response($!) - @response['Cache-Control'] = 'private' - @response['Status'] = 500 - @response.body = conn_data.send_string - @response['content-type'] = conn_data.send_contenttype || @mediatype + @soaplet.do_POST(req, res) + rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex + res.set_error(ex) + rescue HTTPStatus::Error => ex + res.set_error(ex) + rescue HTTPStatus::Status => ex + res.status = ex.code + rescue StandardError, NameError => ex # for Ruby 1.6 + res.set_error(ex, true) ensure - buf = '' - @response.send_response(buf) - buf.sub!(/^[^\r]+\r\n/, '') # Trim status line. + if defined?(MOD_RUBY) + r = Apache.request + r.status = res.status + r.content_type = res.content_type + r.send_http_header + buf = res.body + else + buf = '' + res.send_response(buf) + buf.sub!(/^[^\r]+\r\n/, '') # Trim status line. + end @log.debug { "SOAP CGI Response:\n#{ buf }" } - print buf - epilogue + if @fcgi + @fcgi.out.print buf + @fcgi.finish + @fcgi = nil + else + print buf + end end - 0 end - - def prologue; end - def epilogue; end end diff --git a/lib/soap/rpc/driver.rb b/lib/soap/rpc/driver.rb index 5fd755c51a..cb10ed92b5 100644 --- a/lib/soap/rpc/driver.rb +++ b/lib/soap/rpc/driver.rb @@ -1,5 +1,5 @@ # SOAP4R - SOAP RPC driver -# Copyright (C) 2000, 2001, 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2000, 2001, 2003-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -22,32 +22,55 @@ module RPC class Driver - class EmptyResponseError < Error; end - class << self - def __attr_proxy(symbol, assignable = false) - name = symbol.to_s - self.__send__(:define_method, name, proc { - @servant.__send__(name) - }) - if assignable - self.__send__(:define_method, name + '=', proc { |rhs| - @servant.__send__(name + '=', rhs) + if RUBY_VERSION >= "1.7.0" + def __attr_proxy(symbol, assignable = false) + name = symbol.to_s + self.__send__(:define_method, name, proc { + @proxy.__send__(name) }) + if assignable + self.__send__(:define_method, name + '=', proc { |rhs| + @proxy.__send__(name + '=', rhs) + }) + end + end + else + def __attr_proxy(symbol, assignable = false) + name = symbol.to_s + module_eval <<-EOS + def #{name} + @proxy.#{name} + end + EOS + if assignable + module_eval <<-EOS + def #{name}=(value) + @proxy.#{name} = value + end + EOS + end end end end - __attr_proxy :options - __attr_proxy :headerhandler - __attr_proxy :streamhandler - __attr_proxy :test_loopback_response __attr_proxy :endpoint_url, true __attr_proxy :mapping_registry, true - __attr_proxy :soapaction, true __attr_proxy :default_encodingstyle, true __attr_proxy :generate_explicit_type, true __attr_proxy :allow_unqualified_element, true + __attr_proxy :headerhandler + __attr_proxy :streamhandler + __attr_proxy :test_loopback_response + __attr_proxy :reset_stream + + attr_reader :proxy + attr_reader :options + attr_accessor :soapaction + + def inspect + "#<#{self.class}:#{@proxy.inspect}>" + end def httpproxy options["protocol.http.proxy"] @@ -81,10 +104,12 @@ class Driver options["protocol.wiredump_file_base"] = wiredump_file_base end - def initialize(endpoint_url, namespace, soapaction = nil) - @servant = Servant__.new(self, endpoint_url, namespace) - @servant.soapaction = soapaction - @proxy = @servant.proxy + def initialize(endpoint_url, namespace = nil, soapaction = nil) + @namespace = namespace + @soapaction = soapaction + @options = setup_options + @wiredump_file_base = nil + @proxy = Proxy.new(endpoint_url, @soapaction, @options) end def loadproperty(propertyname) @@ -93,28 +118,23 @@ class Driver end end - def inspect - "#<#{self.class}:#{@servant.inspect}>" - end - def add_rpc_method(name, *params) - param_def = create_rpc_param_def(params) - @servant.add_rpc_method(name, @servant.soapaction, name, param_def) + add_rpc_method_with_soapaction_as(name, name, @soapaction, *params) end def add_rpc_method_as(name, name_as, *params) - param_def = create_rpc_param_def(params) - @servant.add_rpc_method(name_as, @servant.soapaction, name, param_def) + add_rpc_method_with_soapaction_as(name, name_as, @soapaction, *params) end def add_rpc_method_with_soapaction(name, soapaction, *params) - param_def = create_rpc_param_def(params) - @servant.add_rpc_method(name, soapaction, name, param_def) + add_rpc_method_with_soapaction_as(name, name, soapaction, *params) end def add_rpc_method_with_soapaction_as(name, name_as, soapaction, *params) - param_def = create_rpc_param_def(params) - @servant.add_rpc_method(name_as, soapaction, name, param_def) + param_def = SOAPMethod.create_rpc_param_def(params) + qname = XSD::QName.new(@namespace, name_as) + @proxy.add_rpc_method(qname, soapaction, name, param_def) + add_rpc_method_interface(name, param_def) end # add_method is for shortcut of typical rpc/encoded method definition. @@ -123,226 +143,107 @@ class Driver alias add_method_with_soapaction add_rpc_method_with_soapaction alias add_method_with_soapaction_as add_rpc_method_with_soapaction_as - def add_document_method(name, req_qname, res_qname) - param_def = create_document_param_def(name, req_qname, res_qname) - @servant.add_document_method(name, @servant.soapaction, name, param_def) - end - - def add_document_method_as(name, name_as, req_qname, res_qname) - param_def = create_document_param_def(name, req_qname, res_qname) - @servant.add_document_method(name_as, @servant.soapaction, name, param_def) - end - - def add_document_method_with_soapaction(name, soapaction, req_qname, - res_qname) - param_def = create_document_param_def(name, req_qname, res_qname) - @servant.add_document_method(name, soapaction, name, param_def) + def add_document_method(name, soapaction, req_qname, res_qname) + param_def = SOAPMethod.create_doc_param_def(req_qname, res_qname) + @proxy.add_document_method(soapaction, name, param_def) + add_document_method_interface(name, param_def) end - def add_document_method_with_soapaction_as(name, name_as, soapaction, - req_qname, res_qname) - param_def = create_document_param_def(name, req_qname, res_qname) - @servant.add_document_method(name_as, soapaction, name, param_def) + def add_rpc_operation(qname, soapaction, name, param_def, opt = {}) + @proxy.add_rpc_operation(qname, soapaction, name, param_def, opt) + add_rpc_method_interface(name, param_def) end - def reset_stream - @servant.reset_stream + def add_document_operation(soapaction, name, param_def, opt = {}) + @proxy.add_document_operation(soapaction, name, param_def, opt) + add_document_method_interface(name, param_def) end def invoke(headers, body) - @servant.invoke(headers, body) + if headers and !headers.is_a?(SOAPHeader) + headers = create_header(headers) + end + set_wiredump_file_base(body.elename.name) + env = @proxy.invoke(headers, body) + if env.nil? + return nil, nil + else + return env.header, env.body + end end def call(name, *params) - @servant.call(name, *params) + set_wiredump_file_base(name) + @proxy.call(name, *params) end private - def create_rpc_param_def(params) - if params.size == 1 and params[0].is_a?(Array) - params[0] - else - SOAPMethod.create_param_def(params) + def set_wiredump_file_base(name) + if @wiredump_file_base + @proxy.set_wiredump_file_base("#{@wiredump_file_base}_#{name}") end end - def create_document_param_def(name, req_qname, res_qname) - [ - ['input', name, [nil, req_qname.namespace, req_qname.name]], - ['output', name, [nil, res_qname.namespace, res_qname.name]] - ] - end - - def add_rpc_method_interface(name, param_def) - @servant.add_rpc_method_interface(name, param_def) - end - - def add_document_method_interface(name, paramname) - @servant.add_document_method_interface(name, paramname) - end - - class Servant__ - attr_reader :proxy - attr_reader :options - attr_accessor :soapaction - - def initialize(host, endpoint_url, namespace) - @host = host - @namespace = namespace - @soapaction = nil - @options = setup_options - @wiredump_file_base = nil - @endpoint_url = endpoint_url - @proxy = Proxy.new(endpoint_url, @soapaction, @options) - end - - def inspect - "#<#{self.class}:#{@proxy.inspect}>" - end - - def endpoint_url - @proxy.endpoint_url - end - - def endpoint_url=(endpoint_url) - @proxy.endpoint_url = endpoint_url - end - - def mapping_registry - @proxy.mapping_registry - end - - def mapping_registry=(mapping_registry) - @proxy.mapping_registry = mapping_registry - end - - def default_encodingstyle - @proxy.default_encodingstyle - end - - def default_encodingstyle=(encodingstyle) - @proxy.default_encodingstyle = encodingstyle - end - - def generate_explicit_type - @proxy.generate_explicit_type - end - - def generate_explicit_type=(generate_explicit_type) - @proxy.generate_explicit_type = generate_explicit_type - end - - def allow_unqualified_element - @proxy.allow_unqualified_element - end - - def allow_unqualified_element=(allow_unqualified_element) - @proxy.allow_unqualified_element = allow_unqualified_element - end - - def headerhandler - @proxy.headerhandler - end - - def streamhandler - @proxy.streamhandler - end - - def test_loopback_response - @proxy.test_loopback_response + def create_header(headers) + header = SOAPHeader.new() + headers.each do |content, mustunderstand, encodingstyle| + header.add(SOAPHeaderItem.new(content, mustunderstand, encodingstyle)) end + header + end - def reset_stream - @proxy.reset_stream + def setup_options + if opt = Property.loadproperty(::SOAP::PropertyName) + opt = opt["client"] end - - def invoke(headers, body) - if headers and !headers.is_a?(SOAPHeader) - headers = create_header(headers) - end - set_wiredump_file_base(body.elename.name) - env = @proxy.invoke(headers, body) - if env.nil? - return nil, nil - else - return env.header, env.body - end + opt ||= Property.new + opt.add_hook("protocol.mandatorycharset") do |key, value| + @proxy.mandatorycharset = value end - - def call(name, *params) - set_wiredump_file_base(name) - @proxy.call(name, *params) + opt.add_hook("protocol.wiredump_file_base") do |key, value| + @wiredump_file_base = value end + opt["protocol.http.charset"] ||= XSD::Charset.encoding_label + opt["protocol.http.proxy"] ||= Env::HTTP_PROXY + opt["protocol.http.no_proxy"] ||= Env::NO_PROXY + opt + end - def add_rpc_method(name_as, soapaction, name, param_def) - qname = XSD::QName.new(@namespace, name_as) - @proxy.add_rpc_method(qname, soapaction, name, param_def) - add_rpc_method_interface(name, param_def) - end + def add_rpc_method_interface(name, param_def) + param_count = RPC::SOAPMethod.param_count(param_def, + RPC::SOAPMethod::IN, RPC::SOAPMethod::INOUT) + add_method_interface(name, param_count) + end - def add_document_method(name_as, soapaction, name, param_def) - qname = XSD::QName.new(@namespace, name_as) - @proxy.add_document_method(qname, soapaction, name, param_def) - add_document_method_interface(name, param_def) - end + def add_document_method_interface(name, param_def) + param_count = RPC::SOAPMethod.param_count(param_def, RPC::SOAPMethod::IN) + add_method_interface(name, param_count) + end - def add_rpc_method_interface(name, param_def) - param_count = 0 - @proxy.operation[name].each_param_name(RPC::SOAPMethod::IN, - RPC::SOAPMethod::INOUT) do |param_name| - param_count += 1 - end - sclass = class << @host; self; end - sclass.__send__(:define_method, name, proc { |*arg| + if RUBY_VERSION > "1.7.0" + def add_method_interface(name, param_count) + ::SOAP::Mapping.define_singleton_method(self, name) do |*arg| unless arg.size == param_count raise ArgumentError.new( - "wrong number of arguments (#{arg.size} for #{param_count})") + "wrong number of arguments (#{arg.size} for #{param_count})") end - @servant.call(name, *arg) - }) - @host.method(name) - end - - def add_document_method_interface(name, paramname) - sclass = class << @host; self; end - sclass.__send__(:define_method, name, proc { |param| - @servant.call(name, param) - }) - @host.method(name) - end - - private - - def set_wiredump_file_base(name) - if @wiredump_file_base - @proxy.set_wiredump_file_base(@wiredump_file_base + "_#{ name }") - end - end - - def create_header(headers) - header = SOAPHeader.new() - headers.each do |content, mustunderstand, encodingstyle| - header.add(SOAPHeaderItem.new(content, mustunderstand, encodingstyle)) - end - header - end - - def setup_options - if opt = Property.loadproperty(::SOAP::PropertyName) - opt = opt["client"] - end - opt ||= Property.new - opt.add_hook("protocol.mandatorycharset") do |key, value| - @proxy.mandatorycharset = value + call(name, *arg) end - opt.add_hook("protocol.wiredump_file_base") do |key, value| - @wiredump_file_base = value - end - opt["protocol.http.charset"] ||= XSD::Charset.encoding_label - opt["protocol.http.proxy"] ||= Env::HTTP_PROXY - opt["protocol.http.no_proxy"] ||= Env::NO_PROXY - opt + self.method(name) + end + else + def add_method_interface(name, param_count) + instance_eval <<-EOS + def #{name}(*arg) + unless arg.size == #{param_count} + raise ArgumentError.new( + "wrong number of arguments (\#{arg.size} for #{param_count})") + end + call(#{name.dump}, *arg) + end + EOS + self.method(name) end end end diff --git a/lib/soap/rpc/element.rb b/lib/soap/rpc/element.rb index 8a2f319293..e6cae2f7e0 100644 --- a/lib/soap/rpc/element.rb +++ b/lib/soap/rpc/element.rb @@ -1,5 +1,5 @@ # SOAP4R - RPC element definition. -# Copyright (C) 2000, 2001, 2003 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2000, 2001, 2003, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -77,6 +77,8 @@ class SOAPMethod < SOAPStruct attr_reader :param_def attr_reader :inparam attr_reader :outparam + attr_reader :retval_name + attr_reader :retval_class_name def initialize(qname, param_def = nil) super(nil) @@ -93,6 +95,7 @@ class SOAPMethod < SOAPStruct @inparam = {} @outparam = {} @retval_name = nil + @retval_class_name = nil init_param(@param_def) if @param_def end @@ -101,12 +104,12 @@ class SOAPMethod < SOAPStruct @outparam_names.size > 0 end - def each_param_name(*type) - @signature.each do |io_type, name, param_type| - if type.include?(io_type) - yield(name) - end - end + def input_params + collect_params(IN, INOUT) + end + + def output_params + collect_params(OUT, INOUT) end def set_param(params) @@ -124,7 +127,30 @@ class SOAPMethod < SOAPStruct end end - def SOAPMethod.create_param_def(param_names) + def SOAPMethod.param_count(param_def, *type) + count = 0 + param_def.each do |io_type, name, param_type| + if type.include?(io_type) + count += 1 + end + end + count + end + + def SOAPMethod.derive_rpc_param_def(obj, name, *param) + if param.size == 1 and param[0].is_a?(Array) + return param[0] + end + if param.empty? + method = obj.method(name) + param_names = (1..method.arity.abs).collect { |i| "p#{i}" } + else + param_names = param + end + create_rpc_param_def(param_names) + end + + def SOAPMethod.create_rpc_param_def(param_names) param_def = [] param_names.each do |param_name| param_def.push([IN, param_name, nil]) @@ -133,8 +159,29 @@ class SOAPMethod < SOAPStruct param_def end + def SOAPMethod.create_doc_param_def(req_qnames, res_qnames) + req_qnames = [req_qnames] if req_qnames.is_a?(XSD::QName) + res_qnames = [res_qnames] if res_qnames.is_a?(XSD::QName) + param_def = [] + req_qnames.each do |qname| + param_def << [IN, qname.name, [nil, qname.namespace, qname.name]] + end + res_qnames.each do |qname| + param_def << [OUT, qname.name, [nil, qname.namespace, qname.name]] + end + param_def + end + private + def collect_params(*type) + names = [] + @signature.each do |io_type, name, param_type| + names << name if type.include?(io_type) + end + names + end + def init_param(param_def) param_def.each do |io_type, name, param_type| case io_type @@ -148,12 +195,20 @@ private @signature.push([INOUT, name, param_type]) @inoutparam_names.push(name) when RETVAL - if (@retval_name) - raise MethodDefinitionError.new('Duplicated retval') + if @retval_name + raise MethodDefinitionError.new('duplicated retval') end @retval_name = name + @retval_class_name = nil + if param_type + if param_type[0].is_a?(String) + @retval_class_name = Mapping.class_from_name(param_type[0]) + else + @retval_class_name = param_type[0] + end + end else - raise MethodDefinitionError.new("Unknown type: #{ io_type }") + raise MethodDefinitionError.new("unknown type: #{io_type}") end end end @@ -168,7 +223,7 @@ class SOAPMethodRequest < SOAPMethod param_value = [] i = 0 params.each do |param| - param_name = "p#{ i }" + param_name = "p#{i}" i += 1 param_def << [IN, param_name, nil] param_value << [param_name, param] @@ -186,9 +241,9 @@ class SOAPMethodRequest < SOAPMethod end def each - each_param_name(IN, INOUT) do |name| + input_params.each do |name| unless @inparam[name] - raise ParameterError.new("Parameter: #{ name } was not given.") + raise ParameterError.new("parameter: #{name} was not given") end yield(name, @inparam[name]) end @@ -200,10 +255,10 @@ class SOAPMethodRequest < SOAPMethod req end - def create_method_response - SOAPMethodResponse.new( - XSD::QName.new(@elename.namespace, @elename.name + 'Response'), - @param_def) + def create_method_response(response_name = nil) + response_name ||= + XSD::QName.new(@elename.namespace, @elename.name + 'Response') + SOAPMethodResponse.new(response_name, @param_def) end private @@ -211,7 +266,7 @@ private def check_elename(qname) # NCName & ruby's method name unless /\A[\w_][\w\d_\-]*\z/ =~ qname.name - raise MethodDefinitionError.new("Element name '#{qname.name}' not allowed") + raise MethodDefinitionError.new("element name '#{qname.name}' not allowed") end end end @@ -236,11 +291,11 @@ class SOAPMethodResponse < SOAPMethod yield(@retval_name, @retval) end - each_param_name(OUT, INOUT) do |param_name| - unless @outparam[param_name] - raise ParameterError.new("Parameter: #{ param_name } was not given.") + output_params.each do |name| + unless @outparam[name] + raise ParameterError.new("parameter: #{name} was not given") end - yield(param_name, @outparam[param_name]) + yield(name, @outparam[name]) end end end diff --git a/lib/soap/rpc/httpserver.rb b/lib/soap/rpc/httpserver.rb index dccf950480..6d2a72ebe3 100644 --- a/lib/soap/rpc/httpserver.rb +++ b/lib/soap/rpc/httpserver.rb @@ -24,55 +24,62 @@ class HTTPServer < Logger::Application super(config[:SOAPHTTPServerApplicationName] || self.class.name) @default_namespace = config[:SOAPDefaultNamespace] @webrick_config = config.dup + self.level = Logger::Severity::ERROR # keep silent by default @webrick_config[:Logger] ||= @log - @server = nil - @soaplet = ::SOAP::RPC::SOAPlet.new - self.level = Logger::Severity::INFO + @log = @webrick_config[:Logger] # sync logger of App and HTTPServer + @router = ::SOAP::RPC::Router.new(self.class.name) + @soaplet = ::SOAP::RPC::SOAPlet.new(@router) on_init + @server = WEBrick::HTTPServer.new(@webrick_config) + @server.mount('/', @soaplet) end def on_init - # define extra methods in derived class. + # do extra initialization in a derived class if needed. end def status - if @server - @server.status - else - nil - end + @server.status if @server end def shutdown @server.shutdown if @server end - + def mapping_registry - @soaplet.app_scope_router.mapping_registry + @router.mapping_registry end def mapping_registry=(mapping_registry) - @soaplet.app_scope_router.mapping_registry = mapping_registry + @router.mapping_registry = mapping_registry + end + + def generate_explicit_type + @router.generate_explicit_type + end + + def generate_explicit_type=(generate_explicit_type) + @router.generate_explicit_type = generate_explicit_type end # servant entry interface - def add_rpc_request_servant(factory, namespace = @default_namespace, - mapping_registry = nil) - @soaplet.add_rpc_request_servant(factory, namespace, mapping_registry) + def add_rpc_request_servant(factory, namespace = @default_namespace) + @router.add_rpc_request_servant(factory, namespace) end def add_rpc_servant(obj, namespace = @default_namespace) - @soaplet.add_rpc_servant(obj, namespace) + @router.add_rpc_servant(obj, namespace) end - def add_rpc_request_headerhandler(factory) - @soaplet.add_rpc_request_headerhandler(factory) + def add_request_headerhandler(factory) + @router.add_request_headerhandler(factory) end - def add_rpc_headerhandler(obj) - @soaplet.add_rpc_headerhandler(obj) + def add_headerhandler(obj) + @router.add_headerhandler(obj) end + alias add_rpc_headerhandler add_headerhandler # method entry interface @@ -81,52 +88,38 @@ class HTTPServer < Logger::Application end alias add_method add_rpc_method - def add_document_method(obj, name, req_qname, res_qname) - opt = {} - opt[:request_style] = opt[:response_style] = :document - opt[:request_use] = opt[:response_use] = :literal - param_def = [ - ['input', req_qname.name, [nil, req_qname.namespace, req_qname.name]], - ['output', req_qname.name, [nil, res_qname.namespace, res_qname.name]] - ] - @soaplet.app_scope_router.add_operation(req_qname, nil, obj, name, - param_def, opt) - end - def add_rpc_method_as(obj, name, name_as, *param) qname = XSD::QName.new(@default_namespace, name_as) soapaction = nil - param_def = create_param_def(obj, name, param) - add_operation(qname, soapaction, obj, name, param_def) + param_def = SOAPMethod.derive_rpc_param_def(obj, name, *param) + @router.add_rpc_operation(obj, qname, soapaction, name, param_def) end alias add_method_as add_rpc_method_as - def add_operation(qname, soapaction, obj, name, param_def, opt = {}) - opt[:request_style] ||= :rpc - opt[:response_style] ||= :rpc - opt[:request_use] ||= :encoded - opt[:response_use] ||= :encoded - @soaplet.app_scope_router.add_operation(qname, soapaction, obj, name, - param_def, opt) + def add_document_method(obj, soapaction, name, req_qnames, res_qnames) + param_def = SOAPMethod.create_doc_param_def(req_qnames, res_qnames) + @router.add_document_operation(obj, soapaction, name, param_def) + end + + def add_rpc_operation(receiver, qname, soapaction, name, param_def, opt = {}) + @router.add_rpc_operation(receiver, qname, soapaction, name, param_def, opt) + end + + def add_rpc_request_operation(factory, qname, soapaction, name, param_def, opt = {}) + @router.add_rpc_request_operation(factory, qname, soapaction, name, param_def, opt) end - def create_param_def(obj, name, param = nil) - if param.nil? or param.empty? - method = obj.method(name) - ::SOAP::RPC::SOAPMethod.create_param_def( - (1..method.arity.abs).collect { |i| "p#{i}" }) - elsif param.size == 1 and param[0].is_a?(Array) - param[0] - else - ::SOAP::RPC::SOAPMethod.create_param_def(param) - end + def add_document_operation(receiver, soapaction, name, param_def, opt = {}) + @router.add_document_operation(receiver, soapaction, name, param_def, opt) + end + + def add_document_request_operation(factory, soapaction, name, param_def, opt = {}) + @router.add_document_request_operation(factory, soapaction, name, param_def, opt) end private def run - @server = WEBrick::HTTPServer.new(@webrick_config) - @server.mount('/', @soaplet) @server.start end end diff --git a/lib/soap/rpc/proxy.rb b/lib/soap/rpc/proxy.rb index ca110664f9..b9d80541af 100644 --- a/lib/soap/rpc/proxy.rb +++ b/lib/soap/rpc/proxy.rb @@ -1,5 +1,5 @@ # SOAP4R - RPC Proxy library. -# Copyright (C) 2000, 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2000, 2003-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -78,66 +78,77 @@ public @streamhandler.test_loopback_response end - def add_rpc_method(qname, soapaction, name, param_def, opt = {}) + def add_rpc_operation(qname, soapaction, name, param_def, opt = {}) + opt[:request_qname] = qname opt[:request_style] ||= :rpc opt[:response_style] ||= :rpc opt[:request_use] ||= :encoded opt[:response_use] ||= :encoded - @operation[name] = Operation.new(qname, soapaction, name, param_def, opt) + @operation[name] = Operation.new(soapaction, param_def, opt) end - def add_document_method(qname, soapaction, name, param_def, opt = {}) + def add_document_operation(soapaction, name, param_def, opt = {}) opt[:request_style] ||= :document opt[:response_style] ||= :document opt[:request_use] ||= :literal opt[:response_use] ||= :literal - @operation[name] = Operation.new(qname, soapaction, name, param_def, opt) + @operation[name] = Operation.new(soapaction, param_def, opt) end # add_method is for shortcut of typical rpc/encoded method definition. - alias add_method add_rpc_method + alias add_method add_rpc_operation + alias add_rpc_method add_rpc_operation + alias add_document_method add_document_operation - def invoke(req_header, req_body, opt = create_options) - req_env = SOAPEnvelope.new(req_header, req_body) - opt[:external_content] = nil - conn_data = marshal(req_env, opt) - if ext = opt[:external_content] - mime = MIMEMessage.new - ext.each do |k, v| - mime.add_attachment(v.data) - end - mime.add_part(conn_data.send_string + "\r\n") - mime.close - conn_data.send_string = mime.content_str - conn_data.send_contenttype = mime.headers['content-type'].str - end - conn_data = @streamhandler.send(@endpoint_url, conn_data, opt[:soapaction]) - if conn_data.receive_string.empty? - return nil - end - unmarshal(conn_data, opt) + def invoke(req_header, req_body, opt = nil) + opt ||= create_options + route(req_header, req_body, opt, opt) end def call(name, *params) unless op_info = @operation[name] - raise MethodDefinitionError, "Method: #{name} not defined." + raise MethodDefinitionError, "method: #{name} not defined" end req_header = create_request_header - req_body = op_info.create_request_body(params, @mapping_registry, - @literal_mapping_registry) - opt = create_options({ + req_body = SOAPBody.new( + op_info.request_body(params, @mapping_registry, @literal_mapping_registry) + ) + reqopt = create_options({ :soapaction => op_info.soapaction || @soapaction, + :default_encodingstyle => op_info.request_default_encodingstyle}) + resopt = create_options({ :default_encodingstyle => op_info.response_default_encodingstyle}) - env = invoke(req_header, req_body, opt) + env = route(req_header, req_body, reqopt, resopt) + raise EmptyResponseError unless env receive_headers(env.header) - raise EmptyResponseError.new("Empty response.") unless env begin check_fault(env.body) rescue ::SOAP::FaultError => e - Mapping.fault2exception(e) + op_info.raise_fault(e, @mapping_registry, @literal_mapping_registry) end - op_info.create_response_obj(env, @mapping_registry, - @literal_mapping_registry) + op_info.response_obj(env.body, @mapping_registry, @literal_mapping_registry) + end + + def route(req_header, req_body, reqopt, resopt) + req_env = SOAPEnvelope.new(req_header, req_body) + reqopt[:external_content] = nil + conn_data = marshal(req_env, reqopt) + if ext = reqopt[:external_content] + mime = MIMEMessage.new + ext.each do |k, v| + mime.add_attachment(v.data) + end + mime.add_part(conn_data.send_string + "\r\n") + mime.close + conn_data.send_string = mime.content_str + conn_data.send_contenttype = mime.headers['content-type'].str + end + conn_data = @streamhandler.send(@endpoint_url, conn_data, + reqopt[:soapaction]) + if conn_data.receive_string.empty? + return nil + end + unmarshal(conn_data, resopt) end def check_fault(body) @@ -217,97 +228,202 @@ private attr_reader :request_use attr_reader :response_use - def initialize(qname, soapaction, name, param_def, opt) + def initialize(soapaction, param_def, opt) @soapaction = soapaction @request_style = opt[:request_style] @response_style = opt[:response_style] @request_use = opt[:request_use] @response_use = opt[:response_use] - @rpc_method_factory = @document_method_name = nil check_style(@request_style) check_style(@response_style) + check_use(@request_use) + check_use(@response_use) if @request_style == :rpc - @rpc_method_factory = SOAPMethodRequest.new(qname, param_def, - @soapaction) + @rpc_request_qname = opt[:request_qname] + if @rpc_request_qname.nil? + raise MethodDefinitionError.new("rpc_request_qname must be given") + end + @rpc_method_factory = + RPC::SOAPMethodRequest.new(@rpc_request_qname, param_def, @soapaction) else - @document_method_name = {} + @doc_request_qnames = [] + @doc_response_qnames = [] param_def.each do |inout, paramname, typeinfo| - klass, namespace, name = typeinfo - case inout.to_s - when "input" - @document_method_name[:input] = ::XSD::QName.new(namespace, name) - when "output" - @document_method_name[:output] = ::XSD::QName.new(namespace, name) + klass_not_used, nsdef, namedef = typeinfo + if namedef.nil? + raise MethodDefinitionError.new("qname must be given") + end + case inout + when SOAPMethod::IN + @doc_request_qnames << XSD::QName.new(nsdef, namedef) + when SOAPMethod::OUT + @doc_response_qnames << XSD::QName.new(nsdef, namedef) else - raise MethodDefinitionError, "unknown type: " + inout + raise MethodDefinitionError.new( + "illegal inout definition for document style: #{inout}") end end end end def request_default_encodingstyle - (@request_style == :rpc) ? EncodingNamespace : LiteralNamespace + (@request_use == :encoded) ? EncodingNamespace : LiteralNamespace end def response_default_encodingstyle - (@response_style == :rpc) ? EncodingNamespace : LiteralNamespace + (@response_use == :encoded) ? EncodingNamespace : LiteralNamespace end - # for rpc - def each_param_name(*target) + def request_body(values, mapping_registry, literal_mapping_registry) if @request_style == :rpc - @rpc_method_factory.each_param_name(*target) do |name| - yield(name) - end + request_rpc(values, mapping_registry, literal_mapping_registry) else - yield(@document_method_name[:input].name) + request_doc(values, mapping_registry, literal_mapping_registry) end end - def create_request_body(values, mapping_registry, literal_mapping_registry) - if @request_style == :rpc - values = Mapping.obj2soap(values, mapping_registry).to_a - method = @rpc_method_factory.dup - params = {} - idx = 0 - method.each_param_name(::SOAP::RPC::SOAPMethod::IN, - ::SOAP::RPC::SOAPMethod::INOUT) do |name| - params[name] = values[idx] || SOAPNil.new - idx += 1 - end - method.set_param(params) - SOAPBody.new(method) + def response_obj(body, mapping_registry, literal_mapping_registry) + if @response_style == :rpc + response_rpc(body, mapping_registry, literal_mapping_registry) else - name = @document_method_name[:input] - document = literal_mapping_registry.obj2soap(values[0], name) - SOAPBody.new(document) + response_doc(body, mapping_registry, literal_mapping_registry) end end - def create_response_obj(env, mapping_registry, literal_mapping_registry) + def raise_fault(e, mapping_registry, literal_mapping_registry) if @response_style == :rpc - ret = env.body.response ? - Mapping.soap2obj(env.body.response, mapping_registry) : nil - if env.body.outparams - outparams = env.body.outparams.collect { |outparam| - Mapping.soap2obj(outparam) - } - [ret].concat(outparams) - else - ret - end + Mapping.fault2exception(e, mapping_registry) else - Mapping.soap2obj(env.body.root_node, literal_mapping_registry) + Mapping.fault2exception(e, literal_mapping_registry) end end private - ALLOWED_STYLE = [:rpc, :document] def check_style(style) - unless ALLOWED_STYLE.include?(style) - raise MethodDefinitionError, "unknown style: " + style + unless [:rpc, :document].include?(style) + raise MethodDefinitionError.new("unknown style: #{style}") + end + end + + def check_use(use) + unless [:encoded, :literal].include?(use) + raise MethodDefinitionError.new("unknown use: #{use}") + end + end + + def request_rpc(values, mapping_registry, literal_mapping_registry) + if @request_use == :encoded + request_rpc_enc(values, mapping_registry) + else + request_rpc_lit(values, literal_mapping_registry) + end + end + + def request_doc(values, mapping_registry, literal_mapping_registry) + if @request_use == :encoded + request_doc_enc(values, mapping_registry) + else + request_doc_lit(values, literal_mapping_registry) + end + end + + def request_rpc_enc(values, mapping_registry) + method = @rpc_method_factory.dup + names = method.input_params + obj = create_request_obj(names, values) + soap = Mapping.obj2soap(obj, mapping_registry, @rpc_request_qname) + method.set_param(soap) + method + end + + def request_rpc_lit(values, mapping_registry) + method = @rpc_method_factory.dup + params = {} + idx = 0 + method.input_params.each do |name| + params[name] = Mapping.obj2soap(values[idx], mapping_registry, + XSD::QName.new(nil, name)) + idx += 1 + end + method.set_param(params) + method + end + + def request_doc_enc(values, mapping_registry) + (0...values.size).collect { |idx| + ele = Mapping.obj2soap(values[idx], mapping_registry) + ele.elename = @doc_request_qnames[idx] + ele + } + end + + def request_doc_lit(values, mapping_registry) + (0...values.size).collect { |idx| + ele = Mapping.obj2soap(values[idx], mapping_registry, + @doc_request_qnames[idx]) + ele.encodingstyle = LiteralNamespace + ele + } + end + + def response_rpc(body, mapping_registry, literal_mapping_registry) + if @response_use == :encoded + response_rpc_enc(body, mapping_registry) + else + response_rpc_lit(body, literal_mapping_registry) + end + end + + def response_doc(body, mapping_registry, literal_mapping_registry) + if @response_use == :encoded + return *response_doc_enc(body, mapping_registry) + else + return *response_doc_lit(body, literal_mapping_registry) + end + end + + def response_rpc_enc(body, mapping_registry) + ret = nil + if body.response + ret = Mapping.soap2obj(body.response, mapping_registry, + @rpc_method_factory.retval_class_name) + end + if body.outparams + outparams = body.outparams.collect { |outparam| + Mapping.soap2obj(outparam, mapping_regisry) + } + [ret].concat(outparams) + else + ret + end + end + + def response_rpc_lit(body, mapping_registry) + body.root_node.collect { |key, value| + Mapping.soap2obj(value, mapping_registry, + @rpc_method_factory.retval_class_name) + } + end + + def response_doc_enc(body, mapping_registry) + body.collect { |key, value| + Mapping.soap2obj(value, mapping_registry) + } + end + + def response_doc_lit(body, mapping_registry) + body.collect { |key, value| + Mapping.soap2obj(value, mapping_registry) + } + end + + def create_request_obj(names, params) + o = Object.new + for idx in 0 ... params.length + o.instance_variable_set('@' + names[idx], params[idx]) end + o end end end diff --git a/lib/soap/rpc/router.rb b/lib/soap/rpc/router.rb index e9147af13a..1d11bc17dc 100644 --- a/lib/soap/rpc/router.rb +++ b/lib/soap/rpc/router.rb @@ -1,5 +1,5 @@ # SOAP4R - RPC Routing library -# Copyright (C) 2001, 2002 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2001, 2002, 2004, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -25,101 +25,229 @@ class Router include SOAP attr_reader :actor - attr_accessor :allow_unqualified_element - attr_accessor :default_encodingstyle attr_accessor :mapping_registry attr_accessor :literal_mapping_registry - attr_reader :headerhandler + attr_accessor :generate_explicit_type def initialize(actor) @actor = actor - @allow_unqualified_element = false - @default_encodingstyle = nil @mapping_registry = nil @headerhandler = Header::HandlerSet.new @literal_mapping_registry = ::SOAP::Mapping::WSDLLiteralRegistry.new - @operation = {} + @generate_explicit_type = true + @operation_by_soapaction = {} + @operation_by_qname = {} + @headerhandlerfactory = [] end - def add_rpc_method(receiver, qname, soapaction, name, param_def, opt = {}) - opt[:request_style] ||= :rpc - opt[:response_style] ||= :rpc - opt[:request_use] ||= :encoded - opt[:response_use] ||= :encoded - add_operation(qname, soapaction, receiver, name, param_def, opt) + ### + ## header handler interface + # + def add_request_headerhandler(factory) + unless factory.respond_to?(:create) + raise TypeError.new("factory must respond to 'create'") + end + @headerhandlerfactory << factory end - def add_document_method(receiver, qname, soapaction, name, param_def, opt = {}) - opt[:request_style] ||= :document - opt[:response_style] ||= :document - opt[:request_use] ||= :encoded - opt[:response_use] ||= :encoded - if opt[:request_style] == :document - inputdef = param_def.find { |inout, paramname, typeinfo| inout == "input" } - klass, nsdef, namedef = inputdef[2] - qname = ::XSD::QName.new(nsdef, namedef) - end - add_operation(qname, soapaction, receiver, name, param_def, opt) + def add_headerhandler(handler) + @headerhandler.add(handler) end - def add_operation(qname, soapaction, receiver, name, param_def, opt) - @operation[fqname(qname)] = Operation.new(qname, soapaction, receiver, - name, param_def, opt) + ### + ## servant definition interface + # + def add_rpc_request_servant(factory, namespace) + unless factory.respond_to?(:create) + raise TypeError.new("factory must respond to 'create'") + end + obj = factory.create # a dummy instance for introspection + ::SOAP::RPC.defined_methods(obj).each do |name| + begin + qname = XSD::QName.new(namespace, name) + param_def = ::SOAP::RPC::SOAPMethod.derive_rpc_param_def(obj, name) + opt = create_styleuse_option(:rpc, :encoded) + add_rpc_request_operation(factory, qname, nil, name, param_def, opt) + rescue SOAP::RPC::MethodDefinitionError => e + p e if $DEBUG + end + end + end + + def add_rpc_servant(obj, namespace) + ::SOAP::RPC.defined_methods(obj).each do |name| + begin + qname = XSD::QName.new(namespace, name) + param_def = ::SOAP::RPC::SOAPMethod.derive_rpc_param_def(obj, name) + opt = create_styleuse_option(:rpc, :encoded) + add_rpc_operation(obj, qname, nil, name, param_def, opt) + rescue SOAP::RPC::MethodDefinitionError => e + p e if $DEBUG + end + end + end + alias add_servant add_rpc_servant + + ### + ## operation definition interface + # + def add_rpc_operation(receiver, qname, soapaction, name, param_def, opt = {}) + ensure_styleuse_option(opt, :rpc, :encoded) + opt[:request_qname] = qname + op = ApplicationScopeOperation.new(soapaction, receiver, name, param_def, + opt) + if opt[:request_style] != :rpc + raise RPCRoutingError.new("illegal request_style given") + end + assign_operation(soapaction, qname, op) + end + alias add_method add_rpc_operation + alias add_rpc_method add_rpc_operation + + def add_rpc_request_operation(factory, qname, soapaction, name, param_def, opt = {}) + ensure_styleuse_option(opt, :rpc, :encoded) + opt[:request_qname] = qname + op = RequestScopeOperation.new(soapaction, factory, name, param_def, opt) + if opt[:request_style] != :rpc + raise RPCRoutingError.new("illegal request_style given") + end + assign_operation(soapaction, qname, op) end - # add_method is for shortcut of typical use="encoded" method definition. - alias add_method add_rpc_method + def add_document_operation(receiver, soapaction, name, param_def, opt = {}) + # + # adopt workaround for doc/lit wrapper method + # (you should consider to simply use rpc/lit service) + # + #unless soapaction + # raise RPCRoutingError.new("soapaction is a must for document method") + #end + ensure_styleuse_option(opt, :document, :literal) + op = ApplicationScopeOperation.new(soapaction, receiver, name, param_def, + opt) + if opt[:request_style] != :document + raise RPCRoutingError.new("illegal request_style given") + end + assign_operation(soapaction, first_input_part_qname(param_def), op) + end + alias add_document_method add_document_operation + + def add_document_request_operation(factory, soapaction, name, param_def, opt = {}) + # + # adopt workaround for doc/lit wrapper method + # (you should consider to simply use rpc/lit service) + # + #unless soapaction + # raise RPCRoutingError.new("soapaction is a must for document method") + #end + ensure_styleuse_option(opt, :document, :literal) + op = RequestScopeOperation.new(soapaction, receiver, name, param_def, opt) + if opt[:request_style] != :document + raise RPCRoutingError.new("illegal request_style given") + end + assign_operation(soapaction, first_input_part_qname(param_def), op) + end def route(conn_data) - soap_response = nil + # we cannot set request_default_encodingsyle before parsing the content. + env = unmarshal(conn_data) + if env.nil? + raise ArgumentError.new("illegal SOAP marshal format") + end + op = lookup_operation(conn_data.soapaction, env.body) + headerhandler = @headerhandler.dup + @headerhandlerfactory.each do |f| + headerhandler.add(f.create) + end + receive_headers(headerhandler, env.header) + soap_response = default_encodingstyle = nil begin - env = unmarshal(conn_data) - if env.nil? - raise ArgumentError.new("Illegal SOAP marshal format.") - end - receive_headers(env.header) - request = env.body.request - op = @operation[fqname(request.elename)] - unless op - raise RPCRoutingError.new("Method: #{request.elename} not supported.") - end - soap_response = op.call(request, @mapping_registry, @literal_mapping_registry) + soap_response = + op.call(env.body, @mapping_registry, @literal_mapping_registry) + default_encodingstyle = op.response_default_encodingstyle rescue Exception soap_response = fault($!) - conn_data.is_fault = true + default_encodingstyle = nil end - marshal(conn_data, op, soap_response) - conn_data + conn_data.is_fault = true if soap_response.is_a?(SOAPFault) + header = call_headers(headerhandler) + body = SOAPBody.new(soap_response) + env = SOAPEnvelope.new(header, body) + marshal(conn_data, env, default_encodingstyle) end # Create fault response string. - def create_fault_response(e, charset = nil) - header = SOAPHeader.new - body = SOAPBody.new(fault(e)) - env = SOAPEnvelope.new(header, body) - opt = options + def create_fault_response(e) + env = SOAPEnvelope.new(SOAPHeader.new, SOAPBody.new(fault(e))) + opt = {} opt[:external_content] = nil - opt[:charset] = charset response_string = Processor.marshal(env, opt) conn_data = StreamHandler::ConnectionData.new(response_string) conn_data.is_fault = true if ext = opt[:external_content] - mime = MIMEMessage.new - ext.each do |k, v| - mime.add_attachment(v.data) - end - mime.add_part(conn_data.send_string + "\r\n") - mime.close - conn_data.send_string = mime.content_str - conn_data.send_contenttype = mime.headers['content-type'].str + mimeize(conn_data, ext) end conn_data end private - def call_headers - headers = @headerhandler.on_outbound + def first_input_part_qname(param_def) + param_def.each do |inout, paramname, typeinfo| + if inout == SOAPMethod::IN + klass, nsdef, namedef = typeinfo + return XSD::QName.new(nsdef, namedef) + end + end + nil + end + + def create_styleuse_option(style, use) + opt = {} + opt[:request_style] = opt[:response_style] = style + opt[:request_use] = opt[:response_use] = use + opt + end + + def ensure_styleuse_option(opt, style, use) + opt[:request_style] ||= style + opt[:response_style] ||= style + opt[:request_use] ||= use + opt[:response_use] ||= use + end + + def assign_operation(soapaction, qname, op) + assigned = false + if soapaction and !soapaction.empty? + @operation_by_soapaction[soapaction] = op + assigned = true + end + if qname + @operation_by_qname[qname] = op + assigned = true + end + unless assigned + raise RPCRoutingError.new("cannot assign operation") + end + end + + def lookup_operation(soapaction, body) + if op = @operation_by_soapaction[soapaction] + return op + end + qname = body.root_node.elename + if op = @operation_by_qname[qname] + return op + end + if soapaction + raise RPCRoutingError.new("operation: #{soapaction} not supported") + else + raise RPCRoutingError.new("operation: #{qname} not supported") + end + end + + def call_headers(headerhandler) + headers = headerhandler.on_outbound if headers.empty? nil else @@ -131,17 +259,17 @@ private end end - def receive_headers(headers) - @headerhandler.on_inbound(headers) if headers + def receive_headers(headerhandler, headers) + headerhandler.on_inbound(headers) if headers end def unmarshal(conn_data) - opt = options + opt = {} contenttype = conn_data.receive_contenttype if /#{MIMEMessage::MultipartContentType}/i =~ contenttype opt[:external_content] = {} mime = MIMEMessage.parse("Content-Type: " + contenttype, - conn_data.receive_string) + conn_data.receive_string) mime.parts.each do |part| value = Attachment.new(part.content) value.contentid = part.contentid @@ -160,28 +288,29 @@ private env end - def marshal(conn_data, op, soap_response) - response_opt = options - response_opt[:external_content] = nil - if op and !conn_data.is_fault and op.response_use == :document - response_opt[:default_encodingstyle] = - ::SOAP::EncodingStyle::ASPDotNetHandler::Namespace - end - header = call_headers - body = SOAPBody.new(soap_response) - env = SOAPEnvelope.new(header, body) - response_string = Processor.marshal(env, response_opt) + def marshal(conn_data, env, default_encodingstyle = nil) + opt = {} + opt[:external_content] = nil + opt[:default_encodingstyle] = default_encodingstyle + opt[:generate_explicit_type] = @generate_explicit_type + response_string = Processor.marshal(env, opt) conn_data.send_string = response_string - if ext = response_opt[:external_content] - mime = MIMEMessage.new - ext.each do |k, v| - mime.add_attachment(v.data) - end - mime.add_part(conn_data.send_string + "\r\n") - mime.close - conn_data.send_string = mime.content_str - conn_data.send_contenttype = mime.headers['content-type'].str + if ext = opt[:external_content] + mimeize(conn_data, ext) end + conn_data + end + + def mimeize(conn_data, ext) + mime = MIMEMessage.new + ext.each do |k, v| + mime.add_attachment(v.data) + end + mime.add_part(conn_data.send_string + "\r\n") + mime.close + conn_data.send_string = mime.content_str + conn_data.send_contenttype = mime.headers['content-type'].str + conn_data end # Create fault response. @@ -194,84 +323,156 @@ private Mapping.obj2soap(detail, @mapping_registry)) end - def fqname(qname) - "#{ qname.namespace }:#{ qname.name }" - end - - def options - opt = {} - opt[:default_encodingstyle] = @default_encodingstyle - if @allow_unqualified_element - opt[:allow_unqualified_element] = true - end - opt - end - class Operation - attr_reader :receiver attr_reader :name attr_reader :soapaction attr_reader :request_style attr_reader :response_style attr_reader :request_use attr_reader :response_use - - def initialize(qname, soapaction, receiver, name, param_def, opt) + + def initialize(soapaction, name, param_def, opt) @soapaction = soapaction - @receiver = receiver @name = name @request_style = opt[:request_style] @response_style = opt[:response_style] @request_use = opt[:request_use] @response_use = opt[:response_use] + check_style(@request_style) + check_style(@response_style) + check_use(@request_use) + check_use(@response_use) if @response_style == :rpc - @rpc_response_factory = - RPC::SOAPMethodRequest.new(qname, param_def, @soapaction) + request_qname = opt[:request_qname] or raise + @rpc_method_factory = + RPC::SOAPMethodRequest.new(request_qname, param_def, @soapaction) + @rpc_response_qname = opt[:response_qname] else - outputdef = param_def.find { |inout, paramname, typeinfo| inout == "output" } - klass, nsdef, namedef = outputdef[2] - @document_response_qname = ::XSD::QName.new(nsdef, namedef) + @doc_request_qnames = [] + @doc_response_qnames = [] + param_def.each do |inout, paramname, typeinfo| + klass, nsdef, namedef = typeinfo + case inout + when SOAPMethod::IN + @doc_request_qnames << XSD::QName.new(nsdef, namedef) + when SOAPMethod::OUT + @doc_response_qnames << XSD::QName.new(nsdef, namedef) + else + raise ArgumentError.new( + "illegal inout definition for document style: #{inout}") + end + end end end - def call(request, mapping_registry, literal_mapping_registry) + def request_default_encodingstyle + (@request_use == :encoded) ? EncodingNamespace : LiteralNamespace + end + + def response_default_encodingstyle + (@response_use == :encoded) ? EncodingNamespace : LiteralNamespace + end + + def call(body, mapping_registry, literal_mapping_registry) if @request_style == :rpc - param = Mapping.soap2obj(request, mapping_registry) - result = rpc_call(request, param) + values = request_rpc(body, mapping_registry, literal_mapping_registry) else - param = Mapping.soap2obj(request, literal_mapping_registry) - result = document_call(request, param) + values = request_document(body, mapping_registry, literal_mapping_registry) end + result = receiver.method(@name.intern).call(*values) + return result if result.is_a?(SOAPFault) if @response_style == :rpc - rpc_response(result, mapping_registry) + response_rpc(result, mapping_registry, literal_mapping_registry) else - document_response(result, literal_mapping_registry) + response_doc(result, mapping_registry, literal_mapping_registry) end end private - def rpc_call(request, param) + def receiver + raise NotImplementedError.new('must be defined in derived class') + end + + def request_rpc(body, mapping_registry, literal_mapping_registry) + request = body.request unless request.is_a?(SOAPStruct) - raise RPCRoutingError.new("Not an RPC style.") + raise RPCRoutingError.new("not an RPC style") end - values = request.collect { |key, value| param[key] } - @receiver.method(@name.intern).call(*values) + if @request_use == :encoded + request_rpc_enc(request, mapping_registry) + else + request_rpc_lit(request, literal_mapping_registry) + end + end + + def request_document(body, mapping_registry, literal_mapping_registry) + # ToDo: compare names with @doc_request_qnames + if @request_use == :encoded + request_doc_enc(body, mapping_registry) + else + request_doc_lit(body, literal_mapping_registry) + end + end + + def request_rpc_enc(request, mapping_registry) + param = Mapping.soap2obj(request, mapping_registry) + request.collect { |key, value| + param[key] + } end - def document_call(request, param) - @receiver.method(@name.intern).call(param) + def request_rpc_lit(request, mapping_registry) + request.collect { |key, value| + Mapping.soap2obj(value, mapping_registry) + } end - def rpc_response(result, mapping_registry) - soap_response = @rpc_response_factory.create_method_response + def request_doc_enc(body, mapping_registry) + body.collect { |key, value| + Mapping.soap2obj(value, mapping_registry) + } + end + + def request_doc_lit(body, mapping_registry) + body.collect { |key, value| + Mapping.soap2obj(value, mapping_registry) + } + end + + def response_rpc(result, mapping_registry, literal_mapping_registry) + if @response_use == :encoded + response_rpc_enc(result, mapping_registry) + else + response_rpc_lit(result, literal_mapping_registry) + end + end + + def response_doc(result, mapping_registry, literal_mapping_registry) + if @doc_response_qnames.size == 1 and !result.is_a?(Array) + result = [result] + end + if result.size != @doc_response_qnames.size + raise "required #{@doc_response_qnames.size} responses " + + "but #{result.size} given" + end + if @response_use == :encoded + response_doc_enc(result, mapping_registry) + else + response_doc_lit(result, literal_mapping_registry) + end + end + + def response_rpc_enc(result, mapping_registry) + soap_response = + @rpc_method_factory.create_method_response(@rpc_response_qname) if soap_response.have_outparam? unless result.is_a?(Array) - raise RPCRoutingError.new("Out parameter was not returned.") + raise RPCRoutingError.new("out parameter was not returned") end outparams = {} i = 1 - soap_response.each_param_name('out', 'inout') do |outparam| + soap_response.output_params.each do |outparam| outparams[outparam] = Mapping.obj2soap(result[i], mapping_registry) i += 1 end @@ -283,8 +484,83 @@ private soap_response end - def document_response(result, literal_mapping_registry) - literal_mapping_registry.obj2soap(result, @document_response_qname) + def response_rpc_lit(result, mapping_registry) + soap_response = + @rpc_method_factory.create_method_response(@rpc_response_qname) + if soap_response.have_outparam? + unless result.is_a?(Array) + raise RPCRoutingError.new("out parameter was not returned") + end + outparams = {} + i = 1 + soap_response.output_params.each do |outparam| + outparams[outparam] = Mapping.obj2soap(result[i], mapping_registry, + XSD::QName.new(nil, outparam)) + i += 1 + end + soap_response.set_outparam(outparams) + soap_response.retval = Mapping.obj2soap(result[0], mapping_registry, + XSD::QName.new(nil, soap_response.elename)) + else + soap_response.retval = Mapping.obj2soap(result, mapping_registry, + XSD::QName.new(nil, soap_response.elename)) + end + soap_response + end + + def response_doc_enc(result, mapping_registry) + (0...result.size).collect { |idx| + ele = Mapping.obj2soap(result[idx], mapping_registry) + ele.elename = @doc_response_qnames[idx] + ele + } + end + + def response_doc_lit(result, mapping_registry) + (0...result.size).collect { |idx| + mapping_registry.obj2soap(result[idx], @doc_response_qnames[idx]) + } + end + + def check_style(style) + unless [:rpc, :document].include?(style) + raise ArgumentError.new("unknown style: #{style}") + end + end + + def check_use(use) + unless [:encoded, :literal].include?(use) + raise ArgumentError.new("unknown use: #{use}") + end + end + end + + class ApplicationScopeOperation < Operation + def initialize(soapaction, receiver, name, param_def, opt) + super(soapaction, name, param_def, opt) + @receiver = receiver + end + + private + + def receiver + @receiver + end + end + + class RequestScopeOperation < Operation + def initialize(soapaction, receiver_factory, name, param_def, opt) + super(soapaction, name, param_def, opt) + unless receiver_factory.respond_to?(:create) + raise TypeError.new("factory must respond to 'create'") + end + @receiver_factory = receiver_factory + end + + private + + def receiver + @receiver_factory.create end end end diff --git a/lib/soap/rpc/soaplet.rb b/lib/soap/rpc/soaplet.rb index 8f18c53d3a..4d2538f0b3 100644 --- a/lib/soap/rpc/soaplet.rb +++ b/lib/soap/rpc/soaplet.rb @@ -1,5 +1,5 @@ # SOAP4R - SOAP handler servlet for WEBrick -# Copyright (C) 2001, 2002, 2003, 2004 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2001-2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -14,7 +14,23 @@ begin require 'stringio' require 'zlib' rescue LoadError - STDERR.puts "Loading stringio or zlib failed. No gzipped response support." if $DEBUG + warn("Loading stringio or zlib failed. No gzipped response supported.") if $DEBUG +end + + +warn("Overriding WEBrick::Log#debug") if $DEBUG +require 'webrick/log' +module WEBrick + class Log < BasicLog + alias __debug debug + def debug(msg = nil) + if block_given? and msg.nil? + __debug(yield) + else + __debug(msg) + end + end + end end @@ -24,60 +40,27 @@ module RPC class SOAPlet < WEBrick::HTTPServlet::AbstractServlet public - attr_reader :app_scope_router attr_reader :options - def initialize - @rpc_router_map = {} - @app_scope_router = ::SOAP::RPC::Router.new(self.class.name) - @headerhandlerfactory = [] - @app_scope_headerhandler = nil + def initialize(router = nil) + @router = router || ::SOAP::RPC::Router.new(self.class.name) @options = {} + @config = {} end - def allow_content_encoding_gzip=(allow) - @options[:allow_content_encoding_gzip] = allow - end - - # Add servant factory whose object has request scope. A servant object is - # instanciated for each request. - # - # Bear in mind that servant factories are distinguished by HTTP SOAPAction - # header in request. Client which calls request-scoped servant must have a - # SOAPAction header which is a namespace of the servant factory. - # I mean, use Driver#add_method_with_soapaction instead of Driver#add_method - # at client side. - # - # A factory must respond to :create. - # - def add_rpc_request_servant(factory, namespace, mapping_registry = nil) - unless factory.respond_to?(:create) - raise TypeError.new("factory must respond to 'create'") - end - router = setup_rpc_request_router(namespace) - router.factory = factory - router.mapping_registry = mapping_registry - end - - # Add servant object which has application scope. - def add_rpc_servant(obj, namespace) - router = @app_scope_router - SOAPlet.add_rpc_servant_to_router(router, obj, namespace) - add_rpc_router(namespace, router) + # for backward compatibility + def app_scope_router + @router end - alias add_servant add_rpc_servant - def add_rpc_request_headerhandler(factory) - unless factory.respond_to?(:create) - raise TypeError.new("factory must respond to 'create'") - end - @headerhandlerfactory << factory + # for backward compatibility + def add_servant(obj, namespace) + @router.add_rpc_servant(obj, namespace) end - def add_rpc_headerhandler(obj) - @app_scope_headerhandler = obj + def allow_content_encoding_gzip=(allow) + @options[:allow_content_encoding_gzip] = allow end - alias add_headerhandler add_rpc_headerhandler ### ## Servlet interfaces for WEBrick. @@ -93,111 +76,63 @@ public def do_GET(req, res) res.header['Allow'] = 'POST' - raise WEBrick::HTTPStatus::MethodNotAllowed, "GET request not allowed." + raise WEBrick::HTTPStatus::MethodNotAllowed, "GET request not allowed" end def do_POST(req, res) - @config[:Logger].debug { "SOAP request: " + req.body } - soapaction = parse_soapaction(req.meta_vars['HTTP_SOAPACTION']) - router = lookup_router(soapaction) - with_headerhandler(router) do |router| - begin - conn_data = ::SOAP::StreamHandler::ConnectionData.new - conn_data.receive_string = req.body - conn_data.receive_contenttype = req['content-type'] - conn_data = router.route(conn_data) - res['content-type'] = conn_data.send_contenttype - if conn_data.is_fault - res.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR - end - if outstring = encode_gzip(req, conn_data.send_string) - res['content-encoding'] = 'gzip' - res['content-length'] = outstring.size - res.body = outstring - else - res.body = conn_data.send_string - end - rescue Exception => e - conn_data = router.create_fault_response(e) - res.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR - res.body = conn_data.send_string - res['content-type'] = conn_data.send_contenttype || "text/xml" - end + logger.debug { "SOAP request: " + req.body } if logger + begin + conn_data = ::SOAP::StreamHandler::ConnectionData.new + setup_req(conn_data, req) + conn_data = @router.route(conn_data) + setup_res(conn_data, req, res) + rescue Exception => e + conn_data = @router.create_fault_response(e) + res.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR + res.body = conn_data.send_string + res['content-type'] = conn_data.send_contenttype || "text/xml" end - if res.body.is_a?(IO) res.chunked = true - @config[:Logger].debug { "SOAP response: (chunked response not logged)" } + logger.debug { "SOAP response: (chunked response not logged)" } if logger else - @config[:Logger].debug { "SOAP response: " + res.body } + logger.debug { "SOAP response: " + res.body } if logger end end private - class RequestRouter < ::SOAP::RPC::Router - attr_accessor :factory - - def initialize(style = :rpc, namespace = nil) - super(namespace) - @style = style - @namespace = namespace - @factory = nil - end - - def route(soap_string) - obj = @factory.create - namespace = self.actor - router = ::SOAP::RPC::Router.new(@namespace) - if @style == :rpc - SOAPlet.add_rpc_servant_to_router(router, obj, namespace) - else - raise RuntimeError.new("'document' style not supported.") - end - router.route(soap_string) - end - end - - def setup_rpc_request_router(namespace) - router = @rpc_router_map[namespace] || RequestRouter.new(:rpc, namespace) - add_rpc_router(namespace, router) - router + def logger + @config[:Logger] end - def add_rpc_router(namespace, router) - @rpc_router_map[namespace] = router + def setup_req(conn_data, req) + conn_data.receive_string = req.body + conn_data.receive_contenttype = req['content-type'] + conn_data.soapaction = parse_soapaction(req.meta_vars['HTTP_SOAPACTION']) end - def parse_soapaction(soapaction) - if /^"(.*)"$/ =~ soapaction - soapaction = $1 - end - if soapaction.empty? - return nil + def setup_res(conn_data, req, res) + res['content-type'] = conn_data.send_contenttype + if conn_data.is_fault + res.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR end - soapaction - end - - def lookup_router(namespace) - if namespace - @rpc_router_map[namespace] || @app_scope_router + if outstring = encode_gzip(req, conn_data.send_string) + res['content-encoding'] = 'gzip' + res['content-length'] = outstring.size + res.body = outstring else - @app_scope_router + res.body = conn_data.send_string end end - def with_headerhandler(router) - if @app_scope_headerhandler and - !router.headerhandler.include?(@app_scope_headerhandler) - router.headerhandler.add(@app_scope_headerhandler) - end - handlers = @headerhandlerfactory.collect { |f| f.create } - begin - handlers.each { |h| router.headerhandler.add(h) } - yield(router) - ensure - handlers.each { |h| router.headerhandler.delete(h) } + def parse_soapaction(soapaction) + if !soapaction.nil? and !soapaction.empty? + if /^"(.+)"$/ =~ soapaction + return $1 + end end + nil end def encode_gzip(req, outstring) @@ -219,32 +154,6 @@ private req['accept-encoding'] and req['accept-encoding'].split(/,\s*/).include?('gzip') end - - class << self - public - def add_rpc_servant_to_router(router, obj, namespace) - ::SOAP::RPC.defined_methods(obj).each do |name| - begin - add_rpc_servant_method_to_router(router, obj, namespace, name) - rescue SOAP::RPC::MethodDefinitionError => e - p e if $DEBUG - end - end - end - - def add_rpc_servant_method_to_router(router, obj, namespace, name, - style = :rpc, use = :encoded) - qname = XSD::QName.new(namespace, name) - soapaction = nil - method = obj.method(name) - param_def = ::SOAP::RPC::SOAPMethod.create_param_def( - (1..method.arity.abs).collect { |i| "p#{ i }" }) - opt = {} - opt[:request_style] = opt[:response_style] = style - opt[:request_use] = opt[:response_use] = use - router.add_operation(qname, soapaction, obj, name, param_def, opt) - end - end end diff --git a/lib/soap/soap.rb b/lib/soap/soap.rb index 66ef1454a3..0072a8dcf0 100644 --- a/lib/soap/soap.rb +++ b/lib/soap/soap.rb @@ -13,7 +13,7 @@ require 'xsd/charset' module SOAP -Version = '1.5.3-ruby1.8.2' +VERSION = Version = '1.5.4' PropertyName = 'soap/property' EnvelopeNamespace = 'http://schemas.xmlsoap.org/soap/envelope/' @@ -75,6 +75,7 @@ class ArrayIndexOutOfBoundsError < Error; end class ArrayStoreError < Error; end class RPCRoutingError < Error; end +class EmptyResponseError < Error; end class UnhandledMustUnderstandHeaderError < Error; end @@ -101,6 +102,7 @@ class FaultError < Error end end + module Env def self.getenv(name) ENV[name.downcase] || ENV[name.upcase] @@ -113,3 +115,25 @@ end end + + +unless Object.respond_to?(:instance_variable_get) + class Object + def instance_variable_get(ivarname) + instance_eval(ivarname) + end + + def instance_variable_set(ivarname, value) + instance_eval("#{ivarname} = value") + end + end +end + + +unless Kernel.respond_to?(:warn) + module Kernel + def warn(msg) + STDERR.puts(msg + "\n") unless $VERBOSE.nil? + end + end +end diff --git a/lib/soap/streamHandler.rb b/lib/soap/streamHandler.rb index ac2f54d5c9..3890ac263f 100644 --- a/lib/soap/streamHandler.rb +++ b/lib/soap/streamHandler.rb @@ -7,12 +7,12 @@ require 'soap/soap' -require 'soap/property' +require 'soap/httpconfigloader' begin require 'stringio' require 'zlib' rescue LoadError - STDERR.puts "Loading stringio or zlib failed. No gzipped response support." if $DEBUG + warn("Loading stringio or zlib failed. No gzipped response support.") if $DEBUG end @@ -27,7 +27,7 @@ class StreamHandler end HTTPAccess2::Client rescue LoadError - STDERR.puts "Loading http-access2 failed. Net/http is used." if $DEBUG + warn("Loading http-access2 failed. Net/http is used.") if $DEBUG require 'soap/netHttpClient' SOAP::NetHttpClient end @@ -40,6 +40,7 @@ class StreamHandler attr_accessor :receive_string attr_accessor :receive_contenttype attr_accessor :is_fault + attr_accessor :soapaction def initialize(send_string = nil) @send_string = send_string @@ -47,6 +48,7 @@ class StreamHandler @receive_string = nil @receive_contenttype = nil @is_fault = false + @soapaction = nil end end @@ -79,7 +81,7 @@ public super() @client = Client.new(nil, "SOAP4R/#{ Version }") @wiredump_file_base = nil - @charset = @wiredump_dev = @nil + @charset = @wiredump_dev = nil @options = options set_options @client.debug_dev = @wiredump_dev @@ -100,7 +102,8 @@ public end def send(endpoint_url, conn_data, soapaction = nil, charset = @charset) - send_post(endpoint_url, conn_data, soapaction, charset) + conn_data.soapaction ||= soapaction # for backward conpatibility + send_post(endpoint_url, conn_data, charset) end def reset(endpoint_url = nil) @@ -115,24 +118,7 @@ public private def set_options - @client.proxy = @options["proxy"] - @options.add_hook("proxy") do |key, value| - @client.proxy = value - end - @client.no_proxy = @options["no_proxy"] - @options.add_hook("no_proxy") do |key, value| - @client.no_proxy = value - end - if @client.respond_to?(:protocol_version=) - @client.protocol_version = @options["protocol_version"] - @options.add_hook("protocol_version") do |key, value| - @client.protocol_version = value - end - end - set_cookie_store_file(@options["cookie_store_file"]) - @options.add_hook("cookie_store_file") do |key, value| - set_cookie_store_file(value) - end + HTTPConfigLoader.set_options(@client, @options) @charset = @options["charset"] || XSD::Charset.charset_label($KCODE) @options.add_hook("charset") do |key, value| @charset = value @@ -142,96 +128,23 @@ private @wiredump_dev = value @client.debug_dev = @wiredump_dev end - ssl_config = @options["ssl_config"] ||= ::SOAP::Property.new - set_ssl_config(ssl_config) - ssl_config.add_hook(true) do |key, value| - set_ssl_config(ssl_config) - end - basic_auth = @options["basic_auth"] ||= ::SOAP::Property.new - set_basic_auth(basic_auth) - basic_auth.add_hook do |key, value| - set_basic_auth(basic_auth) - end - @options.add_hook("connect_timeout") do |key, value| - @client.connect_timeout = value - end - @options.add_hook("send_timeout") do |key, value| - @client.send_timeout = value - end - @options.add_hook("receive_timeout") do |key, value| - @client.receive_timeout = value + set_cookie_store_file(@options["cookie_store_file"]) + @options.add_hook("cookie_store_file") do |key, value| + set_cookie_store_file(value) end + ssl_config = @options["ssl_config"] + basic_auth = @options["basic_auth"] @options.lock(true) ssl_config.unlock basic_auth.unlock end - def set_basic_auth(basic_auth) - basic_auth.values.each do |url, userid, passwd| - @client.set_basic_auth(url, userid, passwd) - end - end - def set_cookie_store_file(value) @cookie_store = value @client.set_cookie_store(@cookie_store) if @cookie_store end - def set_ssl_config(ssl_config) - ssl_config.each do |key, value| - cfg = @client.ssl_config - case key - when 'client_cert' - cfg.client_cert = cert_from_file(value) - when 'client_key' - cfg.client_key = key_from_file(value) - when 'client_ca' - cfg.client_ca = value - when 'ca_path' - cfg.set_trust_ca(value) - when 'ca_file' - cfg.set_trust_ca(value) - when 'crl' - cfg.set_crl(value) - when 'verify_mode' - cfg.verify_mode = ssl_config_int(value) - when 'verify_depth' - cfg.verify_depth = ssl_config_int(value) - when 'options' - cfg.options = value - when 'ciphers' - cfg.ciphers = value - when 'verify_callback' - cfg.verify_callback = value - when 'cert_store' - cfg.cert_store = value - else - raise ArgumentError.new("unknown ssl_config property #{key}") - end - end - end - - def ssl_config_int(value) - if value.nil? or value.empty? - nil - else - begin - Integer(value) - rescue ArgumentError - ::SOAP::Property::Util.const_from_name(value) - end - end - end - - def cert_from_file(filename) - OpenSSL::X509::Certificate.new(File.open(filename) { |f| f.read }) - end - - def key_from_file(filename) - OpenSSL::PKey::RSA.new(File.open(filename) { |f| f.read }) - end - - def send_post(endpoint_url, conn_data, soapaction, charset) + def send_post(endpoint_url, conn_data, charset) conn_data.send_contenttype ||= StreamHandler.create_media_type(charset) if @wiredump_file_base @@ -243,7 +156,7 @@ private extra = {} extra['Content-Type'] = conn_data.send_contenttype - extra['SOAPAction'] = "\"#{ soapaction }\"" + extra['SOAPAction'] = "\"#{ conn_data.soapaction }\"" extra['Accept-Encoding'] = 'gzip' if send_accept_encoding_gzip? send_string = conn_data.send_string @wiredump_dev << "Wire dump:\n\n" if @wiredump_dev diff --git a/lib/soap/wsdlDriver.rb b/lib/soap/wsdlDriver.rb index f13aa94f94..3431d5d673 100644 --- a/lib/soap/wsdlDriver.rb +++ b/lib/soap/wsdlDriver.rb @@ -1,5 +1,5 @@ # SOAP4R - SOAP WSDL driver -# Copyright (C) 2002, 2003 NAKAMURA, Hiroshi <[email protected]>. +# Copyright (C) 2002, 2003, 2005 NAKAMURA, Hiroshi <[email protected]>. # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; @@ -9,19 +9,11 @@ require 'wsdl/parser' require 'wsdl/importer' require 'xsd/qname' -require 'soap/element' -require 'soap/baseData' -require 'soap/streamHandler' -require 'soap/mimemessage' -require 'soap/mapping' +require 'xsd/codegen/gensupport' require 'soap/mapping/wsdlencodedregistry' require 'soap/mapping/wsdlliteralregistry' -require 'soap/rpc/rpc' -require 'soap/rpc/element' -require 'soap/rpc/proxy' -require 'soap/processor' -require 'soap/header/handlerset' -require 'xsd/codegen/gensupport' +require 'soap/rpc/driver' +require 'wsdl/soap/methodDefCreator' module SOAP @@ -32,60 +24,165 @@ class WSDLDriverFactory attr_reader :wsdl - def initialize(wsdl, logdev = nil) - @logdev = logdev + def initialize(wsdl) @wsdl = import(wsdl) + @methoddefcreator = WSDL::SOAP::MethodDefCreator.new(@wsdl) end def inspect "#<#{self.class}:#{@wsdl.name}>" end + def create_rpc_driver(servicename = nil, portname = nil) + port = find_port(servicename, portname) + drv = SOAP::RPC::Driver.new(port.soap_address.location) + init_driver(drv, port) + add_operation(drv, port) + drv + end + + # depricated old interface def create_driver(servicename = nil, portname = nil) - service = if servicename - @wsdl.service(XSD::QName.new(@wsdl.targetnamespace, servicename)) - else - @wsdl.services[0] - end + warn("WSDLDriverFactory#create_driver is depricated. Use create_rpc_driver instead.") + port = find_port(servicename, portname) + WSDLDriver.new(@wsdl, port, @logdev) + end + + # Backward compatibility. + alias createDriver create_driver + +private + + def find_port(servicename = nil, portname = nil) + service = port = nil + if servicename + service = @wsdl.service( + XSD::QName.new(@wsdl.targetnamespace, servicename)) + else + service = @wsdl.services[0] + end if service.nil? - raise FactoryError.new("Service #{ servicename } not found in WSDL.") + raise FactoryError.new("service #{servicename} not found in WSDL") + end + if portname + port = service.ports[XSD::QName.new(@wsdl.targetnamespace, portname)] + else + port = service.ports[0] 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.") + 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.") + raise FactoryError.new("soap:address element not found in WSDL") end - WSDLDriver.new(@wsdl, port, @logdev) + port end - # Backward compatibility. - alias createDriver create_driver + def init_driver(drv, port) + wsdl_elements = @wsdl.collect_elements + wsdl_types = @wsdl.collect_complextypes + @wsdl.collect_simpletypes + rpc_decode_typemap = wsdl_types + + @wsdl.soap_rpc_complextypes(port.find_binding) + drv.proxy.mapping_registry = + Mapping::WSDLEncodedRegistry.new(rpc_decode_typemap) + drv.proxy.literal_mapping_registry = + Mapping::WSDLLiteralRegistry.new(wsdl_types, wsdl_elements) + end + + def add_operation(drv, port) + port.find_binding.operations.each do |op_bind| + op_name = op_bind.soapoperation_name + soapaction = op_bind.soapaction || '' + orgname = op_name.name + name = XSD::CodeGen::GenSupport.safemethodname(orgname) + param_def = create_param_def(op_bind) + opt = {} + opt[:request_style] = opt[:response_style] = op_bind.soapoperation_style + opt[:request_use] = (op_bind.input.soapbody.use || 'literal').intern + opt[:response_use] = (op_bind.output.soapbody.use || 'literal').intern + if op_bind.soapoperation_style == :rpc + drv.add_rpc_operation(op_name, soapaction, name, param_def, opt) + else + drv.add_document_operation(soapaction, name, param_def, opt) + end + if orgname != name and orgname.capitalize == name.capitalize + ::SOAP::Mapping.define_singleton_method(drv, orgname) do |*arg| + __send__(name, *arg) + end + end + end + end -private - def import(location) WSDL::Importer.import(location) end + + def create_param_def(op_bind) + op = op_bind.find_operation + if op_bind.soapoperation_style == :rpc + param_def = @methoddefcreator.collect_rpcparameter(op) + else + param_def = @methoddefcreator.collect_documentparameter(op) + end + # the first element of typedef in param_def is a String like + # "::SOAP::SOAPStruct". turn this String to a class. + param_def.collect { |io, typedef, name| + typedef[0] = Mapping.class_from_name(typedef[0]) + [io, name, typedef] + } + end + + def partqname(part) + if part.type + part.type + else + part.element + end + end + + def param_def(type, name, klass, partqname) + [type, name, [klass, partqname.namespace, partqname.name]] + end + + def filter_parts(partsdef, partssource) + parts = partsdef.split(/\s+/) + partssource.find_all { |part| parts.include?(part.name) } + end end class WSDLDriver class << self def __attr_proxy(symbol, assignable = false) - name = symbol.to_s - self.__send__(:define_method, name, proc { - @servant.__send__(name) - }) - if assignable - self.__send__(:define_method, name + '=', proc { |rhs| - @servant.__send__(name + '=', rhs) + end + + if RUBY_VERSION >= "1.7.0" + def __attr_proxy(symbol, assignable = false) + name = symbol.to_s + self.__send__(:define_method, name, proc { + @servant.__send__(name) }) + if assignable + self.__send__(:define_method, name + '=', proc { |rhs| + @servant.__send__(name + '=', rhs) + }) + end + end + else + def __attr_proxy(symbol, assignable = false) + name = symbol.to_s + module_eval <<-EOS + def #{name} + @servant.#{name} + end + EOS + if assignable + module_eval <<-EOS + def #{name}=(value) + @servant.#{name} = value + end + EOS + end end end end @@ -179,8 +276,10 @@ class WSDLDriver @wsdl_types = @wsdl.collect_complextypes + @wsdl.collect_simpletypes @rpc_decode_typemap = @wsdl_types + @wsdl.soap_rpc_complextypes(port.find_binding) - @wsdl_mapping_registry = Mapping::WSDLEncodedRegistry.new(@rpc_decode_typemap) - @doc_mapper = Mapping::WSDLLiteralRegistry.new(@wsdl_elements, @wsdl_types) + @wsdl_mapping_registry = Mapping::WSDLEncodedRegistry.new( + @rpc_decode_typemap) + @doc_mapper = Mapping::WSDLLiteralRegistry.new( + @wsdl_types, @wsdl_elements) endpoint_url = @port.soap_address.location # Convert a map which key is QName, to a Hash which key is String. @operation = {} @@ -222,15 +321,16 @@ class WSDLDriver def rpc_call(name, *values) set_wiredump_file_base(name) unless op_info = @operation[name] - raise MethodDefinitionError, "Method: #{name} not defined." + raise RuntimeError, "method: #{name} not defined" end req_header = create_request_header req_body = create_request_body(op_info, *values) - opt = create_options({ - :soapaction => op_info.soapaction || @soapaction, + reqopt = create_options({ + :soapaction => op_info.soapaction || @soapaction}) + resopt = create_options({ :decode_typemap => @rpc_decode_typemap}) - env = @proxy.invoke(req_header, req_body, opt) - raise EmptyResponseError.new("Empty response.") unless env + env = @proxy.route(req_header, req_body, reqopt, resopt) + raise EmptyResponseError unless env receive_headers(env.header) begin @proxy.check_fault(env.body) @@ -249,21 +349,6 @@ class WSDLDriver end end - def document_call(name, param) - set_wiredump_file_base(name) - op_info = @operation[name] - req_header = header_from_obj(header_obj, op_info) - req_body = body_from_obj(body_obj, op_info) - env = @proxy.invoke(req_header, req_body, op_info.soapaction || @soapaction, @wsdl_types) - raise EmptyResponseError.new("Empty response.") unless env - if env.body.fault - raise ::SOAP::FaultError.new(env.body.fault) - end - res_body_obj = env.body.response ? - Mapping.soap2obj(env.body.response, @mapping_registry) : nil - return env.header, res_body_obj - end - # req_header: [[element, mustunderstand, encodingstyle(QName/String)], ...] # req_body: SOAPBasetype/SOAPCompoundtype def document_send(name, header_obj, body_obj) @@ -275,7 +360,7 @@ class WSDLDriver :soapaction => op_info.soapaction || @soapaction, :decode_typemap => @wsdl_types}) env = @proxy.invoke(req_header, req_body, opt) - raise EmptyResponseError.new("Empty response.") unless env + raise EmptyResponseError unless env if env.body.fault raise ::SOAP::FaultError.new(env.body.fault) end @@ -297,7 +382,7 @@ class WSDLDriver def set_wiredump_file_base(name) if @wiredump_file_base - @proxy.set_wiredump_file_base(@wiredump_file_base + "_#{ name }") + @proxy.set_wiredump_file_base(@wiredump_file_base + "_#{name}") end end @@ -326,7 +411,7 @@ class WSDLDriver def create_method_struct(op_info, *params) 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 = Mapping.obj2soap(obj, @wsdl_mapping_registry, op_info.op_name) if method.members.size != parts_names.size new_method = SOAPStruct.new method.each do |key, value| @@ -356,7 +441,7 @@ class WSDLDriver if obj.nil? nil else - raise RuntimeError.new("No header definition in schema.") + raise RuntimeError.new("no header definition in schema: #{obj}") end elsif op_info.headerparts.size == 1 part = op_info.headerparts[0] @@ -366,7 +451,7 @@ class WSDLDriver else header = SOAPHeader.new() op_info.headerparts.each do |part| - child = Mapping.find_attribute(obj, part.name) + child = Mapping.get_attribute(obj, part.name) ele = headeritem_from_obj(child, part.element || part.eletype) header.add(part.name, ele) end @@ -391,7 +476,7 @@ class WSDLDriver if obj.nil? nil else - raise RuntimeError.new("No body found in schema.") + raise RuntimeError.new("no body found in schema") end elsif op_info.bodyparts.size == 1 part = op_info.bodyparts[0] @@ -400,7 +485,7 @@ class WSDLDriver else body = SOAPBody.new op_info.bodyparts.each do |part| - child = Mapping.find_attribute(obj, part.name) + child = Mapping.get_attribute(obj, part.name) ele = bodyitem_from_obj(child, part.element || part.type) body.add(ele.elename.name, ele) end @@ -419,39 +504,40 @@ class WSDLDriver end def add_method_interface(op_info) - name = ::XSD::CodeGen::GenSupport.safemethodname(op_info.op_name.name) + name = XSD::CodeGen::GenSupport.safemethodname(op_info.op_name.name) + orgname = op_info.op_name.name + parts_names = op_info.bodyparts.collect { |part| part.name } case op_info.style when :document - add_document_method_interface(name) + if orgname != name and orgname.capitalize == name.capitalize + add_document_method_interface(orgname, parts_names) + end + add_document_method_interface(name, parts_names) when :rpc - parts_names = op_info.bodyparts.collect { |part| part.name } - orgname = op_info.op_name.name if orgname != name and orgname.capitalize == name.capitalize add_rpc_method_interface(orgname, parts_names) end add_rpc_method_interface(name, parts_names) else - raise RuntimeError.new("Unknown style: #{op_info.style}") + raise RuntimeError.new("unknown style: #{op_info.style}") end end def add_rpc_method_interface(name, parts_names) - sclass = class << @host; self; end - sclass.__send__(:define_method, name, proc { |*arg| + ::SOAP::Mapping.define_singleton_method(@host, name) do |*arg| unless arg.size == parts_names.size raise ArgumentError.new( "wrong number of arguments (#{arg.size} for #{parts_names.size})") end @servant.rpc_call(name, *arg) - }) + end @host.method(name) end - def add_document_method_interface(name) - sclass = class << @host; self; end - sclass.__send__(:define_method, name, proc { |h, b| + def add_document_method_interface(name, parts_names) + ::SOAP::Mapping.define_singleton_method(@host, name) do |*arg| @servant.document_send(name, h, b) - }) + end @host.method(name) end @@ -476,5 +562,3 @@ end end - - |