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/mapping | |
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/mapping')
-rw-r--r-- | lib/soap/mapping/factory.rb | 52 | ||||
-rw-r--r-- | lib/soap/mapping/mapping.rb | 152 | ||||
-rw-r--r-- | lib/soap/mapping/registry.rb | 158 | ||||
-rw-r--r-- | lib/soap/mapping/rubytypeFactory.rb | 41 | ||||
-rw-r--r-- | lib/soap/mapping/wsdlencodedregistry.rb | 182 | ||||
-rw-r--r-- | lib/soap/mapping/wsdlliteralregistry.rb | 337 |
6 files changed, 634 insertions, 288 deletions
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 |