Welcome to the "trac"-ing site of soap4r!
[soap4r] [httpclient] [openpgp4u] [pkcs1] [logger] [csv] [vtr]

root/branches/1_5/lib/soap/mapping/rubytypeFactory.rb

Revision 2016, 15.2 kB (checked in by nahi, 12 hours ago)
  • nil's instance_variables is poisoned when a defined xsi:type in request exists as a class of Ruby (such as 'Struct') but the class cannot be allocated (such as ::Struct). closes #455.
  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 # SOAP4R - Ruby type mapping factory.
2 # Copyright (C) 2000-2007  NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
4 # This program is copyrighted free software by NAKAMURA, Hiroshi.  You can
5 # redistribute it and/or modify it under the same terms of Ruby's license;
6 # either the dual license version in 2003, or any later version.
7
8
9 module SOAP
10 module Mapping
11
12
13 class RubytypeFactory < Factory
14   TYPE_STRING = XSD::QName.new(RubyTypeNamespace, 'String')
15   TYPE_TIME = XSD::QName.new(RubyTypeNamespace, 'Time')
16   TYPE_ARRAY = XSD::QName.new(RubyTypeNamespace, 'Array')
17   TYPE_REGEXP = XSD::QName.new(RubyTypeNamespace, 'Regexp')
18   TYPE_RANGE = XSD::QName.new(RubyTypeNamespace, 'Range')
19   TYPE_CLASS = XSD::QName.new(RubyTypeNamespace, 'Class')
20   TYPE_MODULE = XSD::QName.new(RubyTypeNamespace, 'Module')
21   TYPE_SYMBOL = XSD::QName.new(RubyTypeNamespace, 'Symbol')
22   TYPE_STRUCT = XSD::QName.new(RubyTypeNamespace, 'Struct')
23   TYPE_HASH = XSD::QName.new(RubyTypeNamespace, 'Map')
24
25   def initialize(config = {})
26     @config = config
27     @allow_untyped_struct = @config.key?(:allow_untyped_struct) ?
28       @config[:allow_untyped_struct] : true
29     @allow_original_mapping = @config.key?(:allow_original_mapping) ?
30       @config[:allow_original_mapping] : false
31     @string_factory = StringFactory_.new(true)
32     @basetype_factory = BasetypeFactory_.new(true)
33     @datetime_factory = DateTimeFactory_.new(true)
34     @array_factory = ArrayFactory_.new(true)
35     @hash_factory = HashFactory_.new(true)
36   end
37
38   def obj2soap(soap_class, obj, info, map)
39     param = nil
40     case obj
41     when ::String
42       unless @allow_original_mapping
43         return nil
44       end
45       param = @string_factory.obj2soap(SOAPString, obj, info, map)
46       if obj.class != String
47         param.extraattr[RubyTypeName] = obj.class.name
48       end
49       addiv2soapattr(param, obj, map)
50     when ::Time
51       unless @allow_original_mapping
52         return nil
53       end
54       param = @datetime_factory.obj2soap(SOAPDateTime, obj, info, map)
55       if obj.class != Time
56         param.extraattr[RubyTypeName] = obj.class.name
57       end
58       addiv2soapattr(param, obj, map)
59     when ::Array
60       unless @allow_original_mapping
61         return nil
62       end
63       param = @array_factory.obj2soap(nil, obj, info, map)
64       if obj.class != Array
65         param.extraattr[RubyTypeName] = obj.class.name
66       end
67       addiv2soapattr(param, obj, map)
68     when ::NilClass
69       unless @allow_original_mapping
70         return nil
71       end
72       param = @basetype_factory.obj2soap(SOAPNil, obj, info, map)
73       addiv2soapattr(param, obj, map)
74     when ::FalseClass, ::TrueClass
75       unless @allow_original_mapping
76         return nil
77       end
78       param = @basetype_factory.obj2soap(SOAPBoolean, obj, info, map)
79       addiv2soapattr(param, obj, map)
80     when ::Integer
81       unless @allow_original_mapping
82         return nil
83       end
84       param = @basetype_factory.obj2soap(SOAPInt, obj, info, map)
85       param ||= @basetype_factory.obj2soap(SOAPInteger, obj, info, map)
86       param ||= @basetype_factory.obj2soap(SOAPDecimal, obj, info, map)
87       addiv2soapattr(param, obj, map)
88     when ::Float
89       unless @allow_original_mapping
90         return nil
91       end
92       param = @basetype_factory.obj2soap(SOAPDouble, obj, info, map)
93       if obj.class != Float
94         param.extraattr[RubyTypeName] = obj.class.name
95       end
96       addiv2soapattr(param, obj, map)
97     when ::Hash
98       unless @allow_original_mapping
99         return nil
100       end
101       if obj.respond_to?(:default_proc) && obj.default_proc
102         raise TypeError.new("cannot dump hash with default proc")
103       end
104       param = SOAPStruct.new(TYPE_HASH)
105       mark_marshalled_obj(obj, param)
106       if obj.class != Hash
107         param.extraattr[RubyTypeName] = obj.class.name
108       end
109       obj.each do |key, value|
110         elem = SOAPStruct.new # Undefined type.
111         elem.add("key", Mapping._obj2soap(key, map))
112         elem.add("value", Mapping._obj2soap(value, map))
113         param.add("item", elem)
114       end
115       param.add('default', Mapping._obj2soap(obj.default, map))
116       addiv2soapattr(param, obj, map)
117     when ::Regexp
118       unless @allow_original_mapping
119         return nil
120       end
121       param = SOAPStruct.new(TYPE_REGEXP)
122       mark_marshalled_obj(obj, param)
123       if obj.class != Regexp
124         param.extraattr[RubyTypeName] = obj.class.name
125       end
126       param.add('source', SOAPBase64.new(obj.source))
127       if obj.respond_to?('options')
128         # Regexp#options is from Ruby/1.7
129         options = obj.options
130       else
131         options = 0
132         obj.inspect.sub(/^.*\//, '').each_byte do |c|
133           options += case c
134             when ?i
135               1
136             when ?x
137               2
138             when ?m
139               4
140             when ?n
141               16
142             when ?e
143               32
144             when ?s
145               48
146             when ?u
147               64
148             end
149         end
150       end
151       param.add('options', SOAPInt.new(options))
152       addiv2soapattr(param, obj, map)
153     when ::Range
154       unless @allow_original_mapping
155         return nil
156       end
157       param = SOAPStruct.new(TYPE_RANGE)
158       mark_marshalled_obj(obj, param)
159       if obj.class != Range
160         param.extraattr[RubyTypeName] = obj.class.name
161       end
162       param.add('begin', Mapping._obj2soap(obj.begin, map))
163       param.add('end', Mapping._obj2soap(obj.end, map))
164       param.add('exclude_end', SOAP::SOAPBoolean.new(obj.exclude_end?))
165       addiv2soapattr(param, obj, map)
166     when ::Class
167       unless @allow_original_mapping
168         return nil
169       end
170       if obj.to_s[0] == ?#
171         raise TypeError.new("can't dump anonymous class #{obj}")
172       end
173       param = SOAPStruct.new(TYPE_CLASS)
174       mark_marshalled_obj(obj, param)
175       param.add('name', SOAPString.new(obj.name))
176       addiv2soapattr(param, obj, map)
177     when ::Module
178       unless @allow_original_mapping
179         return nil
180       end
181       if obj.to_s[0] == ?#
182         raise TypeError.new("can't dump anonymous module #{obj}")
183       end
184       param = SOAPStruct.new(TYPE_MODULE)
185       mark_marshalled_obj(obj, param)
186       param.add('name', SOAPString.new(obj.name))
187       addiv2soapattr(param, obj, map)
188     when ::Symbol
189       unless @allow_original_mapping
190         return nil
191       end
192       param = SOAPStruct.new(TYPE_SYMBOL)
193       mark_marshalled_obj(obj, param)
194       param.add('id', SOAPString.new(obj.id2name))
195       addiv2soapattr(param, obj, map)
196     when ::Struct
197       unless @allow_original_mapping
198         # treat it as an user defined class. [ruby-talk:104980]
199         #param = unknownobj2soap(soap_class, obj, info, map)
200         param = SOAPStruct.new(XSD::AnyTypeName)
201         mark_marshalled_obj(obj, param)
202         obj.members.each do |member|
203           param.add(Mapping.name2elename(member),
204             Mapping._obj2soap(obj[member], map))
205         end
206       else
207         param = SOAPStruct.new(TYPE_STRUCT)
208         mark_marshalled_obj(obj, param)
209         param.add('type', ele_type = SOAPString.new(obj.class.to_s))
210         ele_member = SOAPStruct.new
211         obj.members.each do |member|
212           ele_member.add(Mapping.name2elename(member),
213             Mapping._obj2soap(obj[member], map))
214         end
215         param.add('member', ele_member)
216         addiv2soapattr(param, obj, map)
217       end
218     when ::IO, ::Binding, ::Continuation, ::Data, ::Dir, ::File::Stat,
219         ::MatchData, Method, ::Proc, ::Thread, ::ThreadGroup
220         # from 1.8: Process::Status, UnboundMethod
221       return nil
222     when ::SOAP::Mapping::Object
223       param = SOAPStruct.new(XSD::AnyTypeName)
224       mark_marshalled_obj(obj, param)
225       obj.__xmlele.each do |key, value|
226         param.add(key.name, Mapping._obj2soap(value, map))
227       end
228       obj.__xmlattr.each do |key, value|
229         param.extraattr[key] = value
230       end
231     when ::Exception
232       typestr = Mapping.name2elename(obj.class.to_s)
233       param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, typestr))
234       mark_marshalled_obj(obj, param)
235       param.add('message', Mapping._obj2soap(obj.message, map))
236       param.add('backtrace', Mapping._obj2soap(obj.backtrace, map))
237       addiv2soapattr(param, obj, map)
238     else
239       param = unknownobj2soap(soap_class, obj, info, map)
240     end
241     param
242   end
243
244   def soap2obj(obj_class, node, info, map)
245     rubytype = node.extraattr[RubyTypeName]
246     if rubytype or node.type.namespace == RubyTypeNamespace
247       rubytype2obj(node, info, map, rubytype)
248     elsif node.type == XSD::AnyTypeName or node.type == XSD::AnySimpleTypeName
249       anytype2obj(node, info, map)
250     else
251       unknowntype2obj(node, info, map)
252     end
253   end
254
255 private
256
257   def addiv2soapattr(node, obj, map)
258     return if obj.instance_variables.empty?
259     ivars = SOAPStruct.new    # Undefined type.
260     setiv2soap(ivars, obj, map)
261     node.extraattr[RubyIVarName] = ivars
262   end
263
264   def unknownobj2soap(soap_class, obj, info, map)
265     if anonymous_class?(obj)
266       raise TypeError.new("can't dump anonymous class #{obj}")
267     end
268     singleton_class = class << obj; self; end
269     if !singleton_methods_true(obj).empty? or
270         !singleton_class.instance_variables.empty?
271       raise TypeError.new("singleton can't be dumped #{obj}")
272     end
273     if !(singleton_class.ancestors - obj.class.ancestors).empty?
274       typestr = Mapping.name2elename(obj.class.to_s)
275       type = XSD::QName.new(RubyTypeNamespace, typestr)
276     else
277       type = Mapping.class2element(obj.class)
278     end
279     param = SOAPStruct.new(type)
280     mark_marshalled_obj(obj, param)
281     setiv2soap(param, obj, map)
282     param
283   end
284
285   if RUBY_VERSION >= '1.8.0'
286     def singleton_methods_true(obj)
287       obj.singleton_methods(true)
288     end
289   else
290     def singleton_methods_true(obj)
291       obj.singleton_methods
292     end
293   end
294
295   def rubytype2obj(node, info, map, rubytype)
296     klass = rubytype ? Mapping.class_from_name(rubytype) : nil
297     obj = nil
298     case node
299     when SOAPString
300       return @string_factory.soap2obj(klass || String, node, info, map)
301     when SOAPDateTime
302       #return @datetime_factory.soap2obj(klass || Time, node, info, map)
303       klass ||= Time
304       t = node.to_time
305       arg = [t.year, t.month, t.mday, t.hour, t.min, t.sec, t.usec]
306       obj = t.gmt? ? klass.gm(*arg) : klass.local(*arg)
307       mark_unmarshalled_obj(node, obj)
308       return true, obj
309     when SOAPArray
310       return @array_factory.soap2obj(klass || Array, node, info, map)
311     when SOAPNil, SOAPBoolean, SOAPInt, SOAPInteger, SOAPDecimal, SOAPDouble
312       return @basetype_factory.soap2obj(nil, node, info, map)
313     when SOAPStruct
314       return rubytypestruct2obj(node, info, map, rubytype)
315     else
316       raise
317     end
318   end
319
320   def rubytypestruct2obj(node, info, map, rubytype)
321     klass = rubytype ? Mapping.class_from_name(rubytype) : nil
322     obj = nil
323     case node.type
324     when TYPE_HASH
325       klass = rubytype ? Mapping.class_from_name(rubytype) : Hash
326       obj = Mapping.create_empty_object(klass)
327       mark_unmarshalled_obj(node, obj)
328       node.each do |key, value|
329         next unless key == 'item'
330         obj[Mapping._soap2obj(value['key'], map)] =
331           Mapping._soap2obj(value['value'], map)
332       end
333       if node.key?('default')
334         obj.default = Mapping._soap2obj(node['default'], map)
335       end
336     when TYPE_REGEXP
337       klass = rubytype ? Mapping.class_from_name(rubytype) : Regexp
338       obj = Mapping.create_empty_object(klass)
339       mark_unmarshalled_obj(node, obj)
340       source = node['source'].string
341       options = node['options'].data || 0
342       Regexp.instance_method(:initialize).bind(obj).call(source, options)
343     when TYPE_RANGE
344       klass = rubytype ? Mapping.class_from_name(rubytype) : Range
345       obj = Mapping.create_empty_object(klass)
346       mark_unmarshalled_obj(node, obj)
347       first = Mapping._soap2obj(node['begin'], map)
348       last = Mapping._soap2obj(node['end'], map)
349       exclude_end = node['exclude_end'].data
350       Range.instance_method(:initialize).bind(obj).call(first, last, exclude_end)
351     when TYPE_CLASS
352       obj = Mapping.class_from_name(node['name'].data)
353     when TYPE_MODULE
354       obj = Mapping.class_from_name(node['name'].data)
355     when TYPE_SYMBOL
356       obj = node['id'].data.intern
357     when TYPE_STRUCT
358       typestr = Mapping.elename2name(node['type'].data)
359       klass = Mapping.class_from_name(typestr)
360       if klass.nil?
361         return false
362       end
363       unless klass <= ::Struct
364         return false
365       end
366       obj = Mapping.create_empty_object(klass)
367       mark_unmarshalled_obj(node, obj)
368       node['member'].each do |name, value|
369         obj[Mapping.elename2name(name)] = Mapping._soap2obj(value, map)
370       end
371     else
372       return unknowntype2obj(node, info, map)
373     end
374     return true, obj
375   end
376
377   def anytype2obj(node, info, map)
378     case node
379     when SOAPBasetype
380       return true, node.data
381     when SOAPStruct
382       klass = ::SOAP::Mapping::Object
383       obj = klass.new
384       mark_unmarshalled_obj(node, obj)
385       node.each do |name, value|
386         obj.__add_xmlele_value(XSD::QName.new(nil, name),
387           Mapping._soap2obj(value, map))
388       end
389       unless node.extraattr.empty?
390         obj.instance_variable_set('@__xmlattr', node.extraattr)
391       end
392       return true, obj
393     else
394       return false
395     end
396   end
397
398   def unknowntype2obj(node, info, map)
399     case node
400     when SOAPBasetype
401       return true, node.data
402     when SOAPArray
403       return @array_factory.soap2obj(Array, node, info, map)
404     when SOAPStruct
405       obj = unknownstruct2obj(node, info, map)
406       return true, obj if obj
407       if !@allow_untyped_struct
408         return false
409       end
410       return anytype2obj(node, info, map)
411     else
412       # Basetype which is not defined...
413       return false
414     end
415   end
416
417   def unknownstruct2obj(node, info, map)
418     unless node.type.name
419       return nil
420     end
421     typestr = Mapping.elename2name(node.type.name)
422     klass = Mapping.class_from_name(typestr)
423     if klass.respond_to?(:soap_marshallable) and !klass.soap_marshallable
424       return nil
425     end
426     if klass.nil? and @allow_untyped_struct
427       klass = Mapping.class_from_name(typestr, true)    # lenient
428     end
429     if klass.nil?
430       return nil
431     end
432     if klass <= ::Exception
433       return exception2obj(klass, node, map)
434     end
435     klass_type = Mapping.class2qname(klass)
436     return nil unless node.type.match(klass_type)
437     obj = nil
438     begin
439       obj = Mapping.create_empty_object(klass)
440     rescue
441       # type name "data" tries Data.new which raises TypeError
442       return nil
443     end
444     mark_unmarshalled_obj(node, obj)
445     setiv2obj(obj, node, map)
446     obj
447   end
448
449   def exception2obj(klass, node, map)
450     message = Mapping._soap2obj(node['message'], map)
451     backtrace = Mapping._soap2obj(node['backtrace'], map)
452     obj = Mapping.create_empty_object(klass)
453     obj = obj.exception(message)
454     mark_unmarshalled_obj(node, obj)
455     obj.set_backtrace(backtrace)
456     obj
457   end
458
459   # Only creates empty array.  Do String#replace it with real string.
460   def array2obj(node, map, rubytype)
461     klass = rubytype ? Mapping.class_from_name(rubytype) : Array
462     obj = Mapping.create_empty_object(klass)
463     mark_unmarshalled_obj(node, obj)
464     obj
465   end
466
467   # Only creates empty string.  Do String#replace it with real string.
468   def string2obj(node, map, rubytype)
469     klass = rubytype ? Mapping.class_from_name(rubytype) : String
470     obj = Mapping.create_empty_object(klass)
471     mark_unmarshalled_obj(node, obj)
472     obj
473   end
474 end
475
476
477 end
478 end
Note: See TracBrowser for help on using the browser.