1 | # SOAP4R - Ruby type mapping factory.
|
---|
2 | # Copyright (C) 2000-2003, 2005 NAKAMURA, Hiroshi <[email protected]>.
|
---|
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 obj.class.name.empty?
|
---|
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.nil? and @allow_untyped_struct
|
---|
424 | klass = Mapping.class_from_name(typestr, true) # lenient
|
---|
425 | end
|
---|
426 | if klass.nil?
|
---|
427 | return nil
|
---|
428 | end
|
---|
429 | if klass <= ::Exception
|
---|
430 | return exception2obj(klass, node, map)
|
---|
431 | end
|
---|
432 | klass_type = Mapping.class2qname(klass)
|
---|
433 | return nil unless node.type.match(klass_type)
|
---|
434 | obj = nil
|
---|
435 | begin
|
---|
436 | obj = Mapping.create_empty_object(klass)
|
---|
437 | rescue
|
---|
438 | # type name "data" tries Data.new which raises TypeError
|
---|
439 | nil
|
---|
440 | end
|
---|
441 | mark_unmarshalled_obj(node, obj)
|
---|
442 | setiv2obj(obj, node, map)
|
---|
443 | obj
|
---|
444 | end
|
---|
445 |
|
---|
446 | def exception2obj(klass, node, map)
|
---|
447 | message = Mapping._soap2obj(node['message'], map)
|
---|
448 | backtrace = Mapping._soap2obj(node['backtrace'], map)
|
---|
449 | obj = Mapping.create_empty_object(klass)
|
---|
450 | obj = obj.exception(message)
|
---|
451 | mark_unmarshalled_obj(node, obj)
|
---|
452 | obj.set_backtrace(backtrace)
|
---|
453 | obj
|
---|
454 | end
|
---|
455 |
|
---|
456 | # Only creates empty array. Do String#replace it with real string.
|
---|
457 | def array2obj(node, map, rubytype)
|
---|
458 | klass = rubytype ? Mapping.class_from_name(rubytype) : Array
|
---|
459 | obj = Mapping.create_empty_object(klass)
|
---|
460 | mark_unmarshalled_obj(node, obj)
|
---|
461 | obj
|
---|
462 | end
|
---|
463 |
|
---|
464 | # Only creates empty string. Do String#replace it with real string.
|
---|
465 | def string2obj(node, map, rubytype)
|
---|
466 | klass = rubytype ? Mapping.class_from_name(rubytype) : String
|
---|
467 | obj = Mapping.create_empty_object(klass)
|
---|
468 | mark_unmarshalled_obj(node, obj)
|
---|
469 | obj
|
---|
470 | end
|
---|
471 | end
|
---|
472 |
|
---|
473 |
|
---|
474 | end
|
---|
475 | end
|
---|