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

root/trunk/lib/soap/mapping/mapping.rb

Revision 2000, 15.7 kB (checked in by nahi, 1 year ago)
  • removed backward compatibility definitions for soap4r-1.4.X. closes #445.
    • removed SOAPlet#{app_scope_router,add_servant} -> use methods in HTTPServer instead.
    • removed SOAP::WSDLDriver#generateEncodeType -> use SOAP::WSDLDriver#generate_explicit_type
    • removed SOAP::SOAPGenerator -> use SOAP::Generator instead.
    • removed compatibility method definitions for ruby-1.6.X.
  • rpc/encoded service + detail element without xsi:type attribute caused NameError? since 1.5.6. (#435)
  • extract attr_proxy definition. added soap/attrproxy.rb and wsdl/xmlSchema/ref.rb.
  • 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   def self.create_empty_object(klass)
158     klass.allocate
159   end
160
161   # Allow only (Letter | '_') (Letter | Digit | '-' | '_')* here.
162   # Caution: '.' is not allowed here.
163   # To follow XML spec., it should be NCName.
164   #   (denied chars) => .[0-F][0-F]
165   #   ex. a.b => a.2eb
166   #
167   def self.name2elename(name)
168     name.gsub(/([^a-zA-Z0-9:_\-]+)/n) {
169       '.' << $1.unpack('H2' * $1.size).join('.')
170     }.gsub(/::/n, '..')
171   end
172
173   def self.elename2name(name)
174     name.gsub(/\.\./n, '::').gsub(/((?:\.[0-9a-fA-F]{2})+)/n) {
175       [$1.delete('.')].pack('H*')
176     }
177   end
178
179   def self.const_from_name(name, lenient = false)
180     const = ::Object
181     name.sub(/\A::/, '').split('::').each do |const_str|
182       if /\A[A-Z]/ =~ const_str
183         begin
184           if const.const_defined?(const_str)
185             const = const.const_get(const_str)
186             next
187           end
188         rescue NameError
189         end
190       end
191       if lenient
192         const_str = Mapping.safeconstname(const_str)
193         if const.const_defined?(const_str)
194           const = const.const_get(const_str)
195           next
196         end
197       end
198       return nil
199     end
200     const
201   end
202
203   def self.class_from_name(name, lenient = false)
204     unless lenient
205       const = const_from_name_nonlenient(name)
206     else
207       const = const_from_name(name, true)
208     end
209     if const.is_a?(::Class)
210       const
211     else
212       nil
213     end
214   end
215
216   def self.module_from_name(name, lenient = false)
217     unless lenient
218       const = const_from_name_nonlenient(name)
219     else
220       const = const_from_name(name, true)
221     end
222     if const.is_a?(::Module)
223       const
224     else
225       nil
226     end
227   end
228
229   def self.const_from_name_nonlenient(name)
230     if Thread.current[:SOAPMapping]
231       Thread.current[:SOAPMapping][:ConstFromName][name] ||=
232         const_from_name(name)
233     else
234       const_from_name(name)
235     end
236   end
237
238   def self.class2qname(klass)
239     name = schema_type_definition(klass)
240     namespace = schema_ns_definition(klass)
241     XSD::QName.new(namespace, name)
242   end
243
244   def self.class2element(klass)
245     name = schema_type_definition(klass) ||
246       Mapping.name2elename(klass.name)
247     namespace = schema_ns_definition(klass) || RubyCustomTypeNamespace
248     XSD::QName.new(namespace, name)
249   end
250
251   def self.obj2element(obj)
252     name = namespace = nil
253     ivars = obj.instance_variables
254     if ivars.include?('@schema_type')
255       name = obj.instance_variable_get('@schema_type')
256     end
257     if ivars.include?('@schema_ns')
258       namespace = obj.instance_variable_get('@schema_ns')
259     end
260     if !name or !namespace
261       class2qname(obj.class)
262     else
263       XSD::QName.new(namespace, name)
264     end
265   end
266
267   def self.to_qname(obj, ns = nil)
268     if obj.is_a?(XSD::QName)
269       obj
270     else
271       XSD::QName.new(ns, obj)
272     end
273   end
274
275   def self.define_singleton_method(obj, name, &block)
276     sclass = (class << obj; self; end)
277     sclass.class_eval {
278       define_method(name, &block)
279     }
280   end
281
282   def self.get_attributes(obj)
283     if obj.is_a?(::Hash)
284       obj
285     else
286       rs = {}
287       obj.instance_variables.each do |ele|
288         rs[ele.sub(/^@/, '')] = obj.instance_variable_get(ele)
289       end
290       rs
291     end
292   end
293
294   EMPTY_ATTRIBUTES = {}.freeze
295   def self.get_attributes_for_any(obj)
296     if obj.respond_to?(:__xmlele_any)
297       obj.__xmlele_any || EMPTY_ATTRIBUTES
298     else
299       get_attributes(obj)
300     end
301   end
302
303   def self.get_attribute(obj, attr_name)
304     case obj
305     when ::SOAP::Mapping::Object
306       return obj[attr_name]
307     when ::Hash
308       return obj[attr_name] || obj[attr_name.intern]
309     else
310       if obj.respond_to?(attr_name)
311         return obj.__send__(attr_name)
312       end
313       iv = obj.instance_variables
314       name = Mapping.safevarname(attr_name)
315       if iv.include?("@#{name}")
316         return obj.instance_variable_get("@#{name}")
317       elsif iv.include?("@#{attr_name}")
318         return obj.instance_variable_get("@#{attr_name}")
319       end
320       if obj.respond_to?(name)
321         return obj.__send__(name)
322       end
323       nil
324     end
325   end
326
327   def self.set_attributes(obj, values)
328     case obj
329     when ::SOAP::Mapping::Object
330       values.each do |attr_name, value|
331         obj.__add_xmlele_value(attr_name, value)
332       end
333     else
334       values.each do |attr_name, value|
335         # untaint depends GenSupport.safevarname
336         name = Mapping.safevarname(attr_name).untaint
337         setter = name + "="
338         if obj.respond_to?(setter)
339           obj.__send__(setter, value)
340         else
341           obj.instance_variable_set('@' + name, value)
342           begin
343             unless obj.respond_to?(name)
344               obj.instance_eval <<-EOS
345                 def #{name}
346                   @#{name}
347                 end
348               EOS
349             end
350             unless self.respond_to?(name + "=")
351               obj.instance_eval <<-EOS
352                 def #{name}=(value)
353                   @#{name} = value
354                 end
355               EOS
356             end
357           rescue TypeError
358             # singleton class may not exist (e.g. Float)
359           end
360         end
361       end
362     end
363   end
364
365   def self.safeconstname(name)
366     Thread.current[:SOAPMapping][:SafeConstName][name] ||=
367       XSD::CodeGen::GenSupport.safeconstname(name)
368   end
369
370   def self.safemethodname(name)
371     Thread.current[:SOAPMapping][:SafeMethodName][name] ||=
372       XSD::CodeGen::GenSupport.safemethodname(name)
373   end
374
375   def self.safevarname(name)
376     Thread.current[:SOAPMapping][:SafeVarName][name] ||=
377       XSD::CodeGen::GenSupport.safevarname(name)
378   end
379
380   def self.root_type_hint
381     Thread.current[:SOAPMapping][:RootTypeHint]
382   end
383
384   def self.reset_root_type_hint
385     Thread.current[:SOAPMapping][:RootTypeHint] = false
386   end
387
388   def self.external_ces
389     Thread.current[:SOAPMapping][:ExternalCES]
390   end
391
392   def self.schema_ns_definition(klass)
393     class_schema_variable(:schema_ns, klass)
394   end
395
396   def self.schema_name_definition(klass)
397     class_schema_variable(:schema_name, klass)
398   end
399
400   def self.schema_type_definition(klass)
401     class_schema_variable(:schema_type, klass)
402   end
403
404   def self.schema_qualified_definition(klass)
405     class_schema_variable(:schema_qualified, klass)
406   end
407
408   def self.schema_element_definition(klass)
409     class_schema_variable(:schema_element, klass)
410   end
411
412   def self.schema_attribute_definition(klass)
413     class_schema_variable(:schema_attribute, klass)
414   end
415
416   def self.schema_definition_classdef(klass)
417     if Thread.current[:SOAPMapping][:SchemaDefinition].key?(klass)
418       return Thread.current[:SOAPMapping][:SchemaDefinition][klass]
419     end
420     schema_ns = schema_ns_definition(klass)
421     schema_name = schema_name_definition(klass)
422     schema_type = schema_type_definition(klass)
423     qualified = schema_qualified_definition(klass)
424     elements = schema_element_definition(klass)
425     attributes = schema_attribute_definition(klass)
426     return nil if schema_name.nil? and schema_type.nil?
427     schema_name = Mapping.to_qname(schema_name, schema_ns) if schema_name
428     schema_type = Mapping.to_qname(schema_type, schema_ns) if schema_type
429     definition = create_schema_definition(klass,
430       :schema_name => schema_name,
431       :schema_type => schema_type,
432       :is_anonymous => false,
433       :schema_qualified => qualified,
434       :schema_element => elements,
435       :schema_attribute => attributes
436     )
437     Thread.current[:SOAPMapping][:SchemaDefinition][klass] = definition
438     definition
439   end
440
441   def self.create_schema_definition(klass, definition)
442     schema_ns = definition[:schema_ns]
443     schema_name = definition[:schema_name]
444     schema_type = definition[:schema_type]
445     is_anonymous = definition[:is_anonymous]
446     schema_basetype = definition[:schema_basetype]
447     schema_qualified = definition[:schema_qualified]
448     schema_element = definition[:schema_element]
449     schema_attributes = definition[:schema_attribute]
450     definition = SchemaDefinition.new(klass, schema_name, schema_type, is_anonymous, schema_qualified)
451     definition.basetype = schema_basetype
452     definition.attributes = schema_attributes
453     if schema_element
454       if schema_element.respond_to?(:is_concrete_definition) and
455           schema_element.is_concrete_definition
456         definition.elements = schema_element
457       else
458         default_ns = schema_name.namespace if schema_name
459         default_ns ||= schema_type.namespace if schema_type
460         definition.elements = parse_schema_definition(schema_element, default_ns)
461         if klass < ::Array
462           definition.elements.set_array
463         end
464       end
465     end
466     definition
467   end
468
469   # returns SchemaComplexTypeDefinition
470   def self.parse_schema_definition(schema_element, default_ns)
471     definition = nil
472     if schema_element[0] == :choice
473       schema_element.shift
474       definition = SchemaChoiceDefinition.new
475     else
476       definition = SchemaSequenceDefinition.new
477     end
478     schema_element.each do |ele|
479       element_definition = parse_schema_element_definition(ele, default_ns)
480       definition << element_definition
481     end
482     definition
483   end
484
485   # returns SchemaElementDefinition
486   def self.parse_schema_element_definition(schema_element, default_ns)
487     if schema_element[0] == :choice
488       parse_schema_definition(schema_element, default_ns)
489     elsif schema_element[0].is_a?(Array)
490       parse_schema_definition(schema_element, default_ns)
491     else
492       varname, info, occurrence = schema_element
493       mapped_class_str, elename = info
494       if occurrence
495         minoccurs, maxoccurs = occurrence
496       else
497         minoccurs, maxoccurs = 1, 1
498       end
499       as_any = as_array = false
500       if /\[\]$/ =~ mapped_class_str
501         mapped_class_str = mapped_class_str.sub(/\[\]$/, '')
502         if mapped_class_str.empty?
503           mapped_class_str = nil
504         end
505         as_array = true
506       end
507       if mapped_class_str
508         mapped_class = Mapping.class_from_name(mapped_class_str)
509         if mapped_class.nil?
510           warn("cannot find mapped class: #{mapped_class_str}")
511         end
512       end
513       if elename == XSD::AnyTypeName
514         as_any = true
515       elsif elename.nil?
516         elename = XSD::QName.new(default_ns, varname)
517       end
518       SchemaElementDefinition.new(
519         varname, mapped_class, elename, minoccurs, maxoccurs, as_any, as_array)
520     end
521   end
522
523   class << Mapping
524   public
525
526     def protect_threadvars(*symbols)
527       backup = {}
528       begin
529         symbols.each do |sym|
530           backup[sym] = Thread.current[sym]
531         end
532         yield
533       ensure
534         symbols.each do |sym|
535           Thread.current[sym] = backup[sym]
536         end
537       end
538     end
539
540   private
541
542     def class_schema_variable(sym, klass)
543       var = "@@#{sym}"
544       klass.class_variables.include?(var) ? klass.class_eval(var) : nil
545     end
546
547     def protect_mapping(opt)
548       protect_threadvars(:SOAPMapping) do
549         data = Thread.current[:SOAPMapping] = {}
550         data[:MarshalKey] = {}
551         data[:ExternalCES] = opt[:external_ces] || XSD::Charset.encoding
552         data[:NoReference] = opt[:no_reference]
553         data[:RootTypeHint] = opt[:root_type_hint]
554         data[:SchemaDefinition] = {}
555         data[:SafeConstName] = {}
556         data[:SafeMethodName] = {}
557         data[:SafeVarName] = {}
558         data[:ConstFromName] = {}
559         yield
560       end
561     end
562
563     def add_md_ary(md_ary, ary, indices, registry)
564       for idx in 0..(ary.size - 1)
565         if ary[idx].is_a?(Array)
566           add_md_ary(md_ary, ary[idx], indices + [idx], registry)
567         else
568           md_ary[*(indices + [idx])] = _obj2soap(ary[idx], registry)
569         end
570       end
571     end
572   end
573 end
574
575
576 end
Note: See TracBrowser for help on using the browser.