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

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

Revision 1987, 16.9 kB (checked in by nahi, 1 year ago)
  • rpc/encoded service + detail element without xsi:type attribute caused NameError? since 1.5.6. closes #435.
  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 # SOAP4R - Ruby type mapping utility.
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 require 'xsd/codegen/gensupport'
10 require 'soap/mapping/schemadefinition'
11
12
13 module SOAP
14
15
16 module Mapping
17   RubyTypeNamespace = 'http://www.ruby-lang.org/xmlns/ruby/type/1.6'
18   RubyTypeInstanceNamespace =
19     'http://www.ruby-lang.org/xmlns/ruby/type-instance'
20   RubyCustomTypeNamespace = 'http://www.ruby-lang.org/xmlns/ruby/type/custom'
21   ApacheSOAPTypeNamespace = 'http://xml.apache.org/xml-soap'
22
23
24   module TraverseSupport
25     def mark_marshalled_obj(obj, soap_obj)
26       raise if obj.nil?
27       Thread.current[:SOAPMapping][:MarshalKey][obj.__id__] = soap_obj
28     end
29
30     def mark_unmarshalled_obj(node, obj)
31       return if obj.nil?
32       # node.id is not Object#id but SOAPReference#id
33       Thread.current[:SOAPMapping][:MarshalKey][node.id] = obj
34     end
35   end
36
37
38   EMPTY_OPT = {}.freeze
39   def self.obj2soap(obj, registry = nil, type = nil, opt = EMPTY_OPT)
40     registry ||= Mapping::DefaultRegistry
41     soap_obj = nil
42     protect_mapping(opt) do
43       soap_obj = _obj2soap(obj, registry, type)
44     end
45     soap_obj
46   end
47
48   def self.objs2soap(objs, registry = nil, types = nil, opt = EMPTY_OPT)
49     registry ||= Mapping::DefaultRegistry
50     ary = []
51     protect_mapping(opt) do
52       0.upto(objs.length - 1) do |idx|
53         type = types ? types[idx] : nil
54         soap = _obj2soap(objs[idx], registry, type)
55         ary << soap
56       end
57     end
58     ary
59   end
60
61   def self.soap2obj(node, registry = nil, klass = nil, opt = EMPTY_OPT)
62     registry ||= Mapping::DefaultRegistry
63     obj = nil
64     protect_mapping(opt) do
65       obj = _soap2obj(node, registry, klass)
66     end
67     obj
68   end
69
70   def self.ary2soap(ary, type_ns = XSD::Namespace, typename = XSD::AnyTypeLiteral, registry = nil, opt = EMPTY_OPT)
71     registry ||= Mapping::DefaultRegistry
72     type = XSD::QName.new(type_ns, typename)
73     soap_ary = SOAPArray.new(ValueArrayName, 1, type)
74     protect_mapping(opt) do
75       ary.each do |ele|
76         soap_ary.add(_obj2soap(ele, registry, type))
77       end
78     end
79     soap_ary
80   end
81
82   def self.ary2md(ary, rank, type_ns = XSD::Namespace, typename = XSD::AnyTypeLiteral, registry = nil, opt = EMPTY_OPT)
83     registry ||= Mapping::DefaultRegistry
84     type = XSD::QName.new(type_ns, typename)
85     md_ary = SOAPArray.new(ValueArrayName, rank, type)
86     protect_mapping(opt) do
87       add_md_ary(md_ary, ary, [], registry)
88     end
89     md_ary
90   end
91
92   def self.fault2exception(fault, registry = nil)
93     registry ||= Mapping::DefaultRegistry
94     detail = ""
95     if fault.detail
96       begin
97         fault.detail.type ||= XSD::QName::EMPTY
98         detail = soap2obj(fault.detail, registry) || ""
99       rescue MappingError
100         detail = fault.detail
101       end
102     end
103     if detail.is_a?(Mapping::SOAPException)
104       begin
105         e = detail.to_e
106         remote_backtrace = e.backtrace
107         e.set_backtrace(nil)
108         raise e # ruby sets current caller as local backtrace of e => e2.
109       rescue Exception => e
110         e.set_backtrace(remote_backtrace + e.backtrace[1..-1])
111         raise
112       end
113     else
114       fault.detail = detail
115       fault.set_backtrace(
116         if detail.is_a?(Array)
117           detail
118         else
119           [detail.to_s]
120         end
121       )
122       raise
123     end
124   end
125
126   def self._obj2soap(obj, registry, type = nil)
127     if obj.respond_to?(:to_xmlpart)
128       SOAPRawData.new(obj)
129     elsif defined?(::REXML) and obj.is_a?(::REXML::Element)
130       SOAPRawData.new(SOAPREXMLElementWrap.new(obj))
131     elsif referent = Thread.current[:SOAPMapping][:MarshalKey][obj.__id__] and
132         !Thread.current[:SOAPMapping][:NoReference]
133       SOAPReference.new(referent)
134     elsif registry
135       registry.obj2soap(obj, type)
136     else
137       raise MappingError.new("no mapping registry given")
138     end
139   end
140
141   def self._soap2obj(node, registry, klass = nil)
142     if node.nil?
143       return nil
144     elsif node.is_a?(SOAPReference)
145       target = node.__getobj__
146       # target.id is not Object#id but SOAPReference#id
147       if referent = Thread.current[:SOAPMapping][:MarshalKey][target.id] and
148           !Thread.current[:SOAPMapping][:NoReference]
149         return referent
150       else
151         return _soap2obj(target, registry, klass)
152       end
153     end
154     return registry.soap2obj(node, klass)
155   end
156
157   if Object.respond_to?(:allocate)
158     # ruby/1.7 or later.
159     def self.create_empty_object(klass)
160       klass.allocate
161     end
162   else
163     MARSHAL_TAG = {
164       String => ['"', 1],
165       Regexp => ['/', 2],
166       Array => ['[', 1],
167       Hash => ['{', 1]
168     }
169     def self.create_empty_object(klass)
170       if klass <= Struct
171         name = klass.name
172         return ::Marshal.load(sprintf("\004\006S:%c%s\000", name.length + 5, name))
173       end
174       if MARSHAL_TAG.has_key?(klass)
175         tag, terminate = MARSHAL_TAG[klass]
176         return ::Marshal.load(sprintf("\004\006%s%s", tag, "\000" * terminate))
177       end
178       MARSHAL_TAG.each do |k, v|
179         if klass < k
180           name = klass.name
181           tag, terminate = v
182           return ::Marshal.load(sprintf("\004\006C:%c%s%s%s", name.length + 5, name, tag, "\000" * terminate))
183         end
184       end
185       name = klass.name
186       ::Marshal.load(sprintf("\004\006o:%c%s\000", name.length + 5, name))
187     end
188   end
189
190   # Allow only (Letter | '_') (Letter | Digit | '-' | '_')* here.
191   # Caution: '.' is not allowed here.
192   # To follow XML spec., it should be NCName.
193   #   (denied chars) => .[0-F][0-F]
194   #   ex. a.b => a.2eb
195   #
196   def self.name2elename(name)
197     name.gsub(/([^a-zA-Z0-9:_\-]+)/n) {
198       '.' << $1.unpack('H2' * $1.size).join('.')
199     }.gsub(/::/n, '..')
200   end
201
202   def self.elename2name(name)
203     name.gsub(/\.\./n, '::').gsub(/((?:\.[0-9a-fA-F]{2})+)/n) {
204       [$1.delete('.')].pack('H*')
205     }
206   end
207
208   def self.const_from_name(name, lenient = false)
209     const = ::Object
210     name.sub(/\A::/, '').split('::').each do |const_str|
211       if /\A[A-Z]/ =~ const_str
212         begin
213           if const.const_defined?(const_str)
214             const = const.const_get(const_str)
215             next
216           end
217         rescue NameError
218         end
219       end
220       if lenient
221         const_str = Mapping.safeconstname(const_str)
222         if const.const_defined?(const_str)
223           const = const.const_get(const_str)
224           next
225         end
226       end
227       return nil
228     end
229     const
230   end
231
232   def self.class_from_name(name, lenient = false)
233     unless lenient
234       const = const_from_name_nonlenient(name)
235     else
236       const = const_from_name(name, true)
237     end
238     if const.is_a?(::Class)
239       const
240     else
241       nil
242     end
243   end
244
245   def self.module_from_name(name, lenient = false)
246     unless lenient
247       const = const_from_name_nonlenient(name)
248     else
249       const = const_from_name(name, true)
250     end
251     if const.is_a?(::Module)
252       const
253     else
254       nil
255     end
256   end
257
258   def self.const_from_name_nonlenient(name)
259     if Thread.current[:SOAPMapping]
260       Thread.current[:SOAPMapping][:ConstFromName][name] ||=
261         const_from_name(name)
262     else
263       const_from_name(name)
264     end
265   end
266
267   def self.class2qname(klass)
268     name = schema_type_definition(klass)
269     namespace = schema_ns_definition(klass)
270     XSD::QName.new(namespace, name)
271   end
272
273   def self.class2element(klass)
274     name = schema_type_definition(klass) ||
275       Mapping.name2elename(klass.name)
276     namespace = schema_ns_definition(klass) || RubyCustomTypeNamespace
277     XSD::QName.new(namespace, name)
278   end
279
280   def self.obj2element(obj)
281     name = namespace = nil
282     ivars = obj.instance_variables
283     if ivars.include?('@schema_type')
284       name = obj.instance_variable_get('@schema_type')
285     end
286     if ivars.include?('@schema_ns')
287       namespace = obj.instance_variable_get('@schema_ns')
288     end
289     if !name or !namespace
290       class2qname(obj.class)
291     else
292       XSD::QName.new(namespace, name)
293     end
294   end
295
296   def self.to_qname(obj, ns = nil)
297     if obj.is_a?(XSD::QName)
298       obj
299     else
300       XSD::QName.new(ns, obj)
301     end
302   end
303
304   def self.define_singleton_method(obj, name, &block)
305     sclass = (class << obj; self; end)
306     sclass.class_eval {
307       define_method(name, &block)
308     }
309   end
310
311   def self.get_attributes(obj)
312     if obj.is_a?(::Hash)
313       obj
314     else
315       rs = {}
316       obj.instance_variables.each do |ele|
317         rs[ele.sub(/^@/, '')] = obj.instance_variable_get(ele)
318       end
319       rs
320     end
321   end
322
323   EMPTY_ATTRIBUTES = {}.freeze
324   def self.get_attributes_for_any(obj)
325     if obj.respond_to?(:__xmlele_any)
326       obj.__xmlele_any || EMPTY_ATTRIBUTES
327     else
328       get_attributes(obj)
329     end
330   end
331
332   def self.get_attribute(obj, attr_name)
333     case obj
334     when ::SOAP::Mapping::Object
335       return obj[attr_name]
336     when ::Hash
337       return obj[attr_name] || obj[attr_name.intern]
338     else
339       if obj.respond_to?(attr_name)
340         return obj.__send__(attr_name)
341       end
342       iv = obj.instance_variables
343       name = Mapping.safevarname(attr_name)
344       if iv.include?("@#{name}")
345         return obj.instance_variable_get("@#{name}")
346       elsif iv.include?("@#{attr_name}")
347         return obj.instance_variable_get("@#{attr_name}")
348       end
349       if obj.respond_to?(name)
350         return obj.__send__(name)
351       end
352       nil
353     end
354   end
355
356   def self.set_attributes(obj, values)
357     case obj
358     when ::SOAP::Mapping::Object
359       values.each do |attr_name, value|
360         obj.__add_xmlele_value(attr_name, value)
361       end
362     else
363       values.each do |attr_name, value|
364         # untaint depends GenSupport.safevarname
365         name = Mapping.safevarname(attr_name).untaint
366         setter = name + "="
367         if obj.respond_to?(setter)
368           obj.__send__(setter, value)
369         else
370           obj.instance_variable_set('@' + name, value)
371           begin
372             unless obj.respond_to?(name)
373               obj.instance_eval <<-EOS
374                 def #{name}
375                   @#{name}
376                 end
377               EOS
378             end
379             unless self.respond_to?(name + "=")
380               obj.instance_eval <<-EOS
381                 def #{name}=(value)
382                   @#{name} = value
383                 end
384               EOS
385             end
386           rescue TypeError
387             # singleton class may not exist (e.g. Float)
388           end
389         end
390       end
391     end
392   end
393
394   def self.safeconstname(name)
395     Thread.current[:SOAPMapping][:SafeConstName][name] ||=
396       XSD::CodeGen::GenSupport.safeconstname(name)
397   end
398
399   def self.safemethodname(name)
400     Thread.current[:SOAPMapping][:SafeMethodName][name] ||=
401       XSD::CodeGen::GenSupport.safemethodname(name)
402   end
403
404   def self.safevarname(name)
405     Thread.current[:SOAPMapping][:SafeVarName][name] ||=
406       XSD::CodeGen::GenSupport.safevarname(name)
407   end
408
409   def self.root_type_hint
410     Thread.current[:SOAPMapping][:RootTypeHint]
411   end
412
413   def self.reset_root_type_hint
414     Thread.current[:SOAPMapping][:RootTypeHint] = false
415   end
416
417   def self.external_ces
418     Thread.current[:SOAPMapping][:ExternalCES]
419   end
420
421   def self.schema_ns_definition(klass)
422     class_schema_variable(:schema_ns, klass)
423   end
424
425   def self.schema_name_definition(klass)
426     class_schema_variable(:schema_name, klass)
427   end
428
429   def self.schema_type_definition(klass)
430     class_schema_variable(:schema_type, klass)
431   end
432
433   def self.schema_qualified_definition(klass)
434     class_schema_variable(:schema_qualified, klass)
435   end
436
437   def self.schema_element_definition(klass)
438     class_schema_variable(:schema_element, klass)
439   end
440
441   def self.schema_attribute_definition(klass)
442     class_schema_variable(:schema_attribute, klass)
443   end
444
445   def self.schema_definition_classdef(klass)
446     if Thread.current[:SOAPMapping][:SchemaDefinition].key?(klass)
447       return Thread.current[:SOAPMapping][:SchemaDefinition][klass]
448     end
449     schema_ns = schema_ns_definition(klass)
450     schema_name = schema_name_definition(klass)
451     schema_type = schema_type_definition(klass)
452     qualified = schema_qualified_definition(klass)
453     elements = schema_element_definition(klass)
454     attributes = schema_attribute_definition(klass)
455     return nil if schema_name.nil? and schema_type.nil?
456     schema_name = Mapping.to_qname(schema_name, schema_ns) if schema_name
457     schema_type = Mapping.to_qname(schema_type, schema_ns) if schema_type
458     definition = create_schema_definition(klass,
459       :schema_name => schema_name,
460       :schema_type => schema_type,
461       :is_anonymous => false,
462       :schema_qualified => qualified,
463       :schema_element => elements,
464       :schema_attribute => attributes
465     )
466     Thread.current[:SOAPMapping][:SchemaDefinition][klass] = definition
467     definition
468   end
469
470   def self.create_schema_definition(klass, definition)
471     schema_ns = definition[:schema_ns]
472     schema_name = definition[:schema_name]
473     schema_type = definition[:schema_type]
474     is_anonymous = definition[:is_anonymous]
475     schema_basetype = definition[:schema_basetype]
476     # wrap if needed for backward compatibility
477     if schema_ns
478       schema_name = Mapping.to_qname(schema_name, schema_ns) if schema_name
479       schema_type = Mapping.to_qname(schema_type, schema_ns) if schema_type
480       # no need for schema_basetype bacause it's introduced later
481     end
482     schema_qualified = definition[:schema_qualified]
483     schema_element = definition[:schema_element]
484     schema_attributes = definition[:schema_attribute]
485     definition = SchemaDefinition.new(klass, schema_name, schema_type, is_anonymous, schema_qualified)
486     definition.basetype = schema_basetype
487     definition.attributes = schema_attributes
488     if schema_element
489       if schema_element.respond_to?(:is_concrete_definition) and
490           schema_element.is_concrete_definition
491         definition.elements = schema_element
492       else
493         default_ns = schema_name.namespace if schema_name
494         default_ns ||= schema_type.namespace if schema_type
495         definition.elements = parse_schema_definition(schema_element, default_ns)
496         if klass < ::Array
497           definition.elements.set_array
498         end
499       end
500     end
501     definition
502   end
503
504   # for backward compatibility
505   # returns SchemaComplexTypeDefinition
506   def self.parse_schema_definition(schema_element, default_ns)
507     definition = nil
508     if schema_element[0] == :choice
509       schema_element.shift
510       definition = SchemaChoiceDefinition.new
511     else
512       definition = SchemaSequenceDefinition.new
513     end
514     schema_element.each do |ele|
515       element_definition = parse_schema_element_definition(ele, default_ns)
516       definition << element_definition
517     end
518     definition
519   end
520
521   # returns SchemaElementDefinition
522   def self.parse_schema_element_definition(schema_element, default_ns)
523     if schema_element[0] == :choice
524       parse_schema_definition(schema_element, default_ns)
525     elsif schema_element[0].is_a?(Array)
526       parse_schema_definition(schema_element, default_ns)
527     else
528       varname, info, occurrence = schema_element
529       mapped_class_str, elename = info
530       if occurrence
531         minoccurs, maxoccurs = occurrence
532       else
533         # for backward compatibility
534         minoccurs, maxoccurs = 1, 1
535       end
536       as_any = as_array = false
537       if /\[\]$/ =~ mapped_class_str
538         mapped_class_str = mapped_class_str.sub(/\[\]$/, '')
539         if mapped_class_str.empty?
540           mapped_class_str = nil
541         end
542         as_array = true
543       end
544       if mapped_class_str
545         mapped_class = Mapping.class_from_name(mapped_class_str)
546         if mapped_class.nil?
547           warn("cannot find mapped class: #{mapped_class_str}")
548         end
549       end
550       if elename == XSD::AnyTypeName
551         as_any = true
552       elsif elename.nil?
553         elename = XSD::QName.new(default_ns, varname)
554       end
555       SchemaElementDefinition.new(
556         varname, mapped_class, elename, minoccurs, maxoccurs, as_any, as_array)
557     end
558   end
559
560   class << Mapping
561   public
562
563     def protect_threadvars(*symbols)
564       backup = {}
565       begin
566         symbols.each do |sym|
567           backup[sym] = Thread.current[sym]
568         end
569         yield
570       ensure
571         symbols.each do |sym|
572           Thread.current[sym] = backup[sym]
573         end
574       end
575     end
576
577   private
578
579     def class_schema_variable(sym, klass)
580       var = "@@#{sym}"
581       klass.class_variables.include?(var) ? klass.class_eval(var) : nil
582     end
583
584     def protect_mapping(opt)
585       protect_threadvars(:SOAPMapping) do
586         data = Thread.current[:SOAPMapping] = {}
587         data[:MarshalKey] = {}
588         data[:ExternalCES] = opt[:external_ces] || XSD::Charset.encoding
589         data[:NoReference] = opt[:no_reference]
590         data[:RootTypeHint] = opt[:root_type_hint]
591         data[:SchemaDefinition] = {}
592         data[:SafeConstName] = {}
593         data[:SafeMethodName] = {}
594         data[:SafeVarName] = {}
595         data[:ConstFromName] = {}
596         yield
597       end
598     end
599
600     def add_md_ary(md_ary, ary, indices, registry)
601       for idx in 0..(ary.size - 1)
602         if ary[idx].is_a?(Array)
603           add_md_ary(md_ary, ary[idx], indices + [idx], registry)
604         else
605           md_ary[*(indices + [idx])] = _obj2soap(ary[idx], registry)
606         end
607       end
608     end
609   end
610 end
611
612
613 end
Note: See TracBrowser for help on using the browser.