summaryrefslogtreecommitdiff
path: root/lib/soap/mapping/mapping.rb
blob: 19eca5dab06eca5386612563a408536a1f5a63f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
=begin
SOAP4R - Ruby type mapping utility.
Copyright (C) 2000, 2001, 2003 NAKAMURA Hiroshi.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PRATICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 675 Mass
Ave, Cambridge, MA 02139, USA.
=end


module SOAP


module Mapping
  RubyTypeNamespace = 'http://www.ruby-lang.org/xmlns/ruby/type/1.6'
  RubyTypeInstanceNamespace =
    'http://www.ruby-lang.org/xmlns/ruby/type-instance'
  RubyCustomTypeNamespace = 'http://www.ruby-lang.org/xmlns/ruby/type/custom'
  ApacheSOAPTypeNamespace = 'http://xml.apache.org/xml-soap'


  # TraverseSupport breaks Thread.current[:SOAPMarshalDataKey].
  module TraverseSupport
    def mark_marshalled_obj(obj, soap_obj)
      Thread.current[:SOAPMarshalDataKey][obj.__id__] = soap_obj
    end

    def mark_unmarshalled_obj(node, obj)
      # node.id is not Object#id but SOAPReference#id
      Thread.current[:SOAPMarshalDataKey][node.id] = obj
    end
  end


  def self.obj2soap(obj, registry = nil, type = nil)
    registry ||= Mapping::DefaultRegistry
    Thread.current[:SOAPMarshalDataKey] = {}
    soap_obj = _obj2soap(obj, registry, type)
    Thread.current[:SOAPMarshalDataKey] = nil
    soap_obj
  end

  def self.soap2obj(node, registry = nil)
    registry ||= Mapping::DefaultRegistry
    Thread.current[:SOAPMarshalDataKey] = {}
    obj = _soap2obj(node, registry)
    Thread.current[:SOAPMarshalDataKey] = nil
    obj
  end

  def self.ary2soap(ary, type_ns = XSD::Namespace, typename = XSD::AnyTypeLiteral, registry = nil)
    registry ||= Mapping::DefaultRegistry
    type = XSD::QName.new(type_ns, typename)
    soap_ary = SOAPArray.new(ValueArrayName, 1, type)
    Thread.current[:SOAPMarshalDataKey] = {}
    ary.each do |ele|
      soap_ary.add(_obj2soap(ele, registry, type))
    end
    Thread.current[:SOAPMarshalDataKey] = nil
    soap_ary
  end

  def self.ary2md(ary, rank, type_ns = XSD::Namespace, typename = XSD::AnyTypeLiteral, registry = nil)
    registry ||= Mapping::DefaultRegistry
    type = XSD::QName.new(type_ns, typename)
    md_ary = SOAPArray.new(ValueArrayName, rank, type)
    Thread.current[:SOAPMarshalDataKey] = {}
    add_md_ary(md_ary, ary, [], registry)
    Thread.current[:SOAPMarshalDataKey] = nil
    md_ary
  end

  def self.fault2exception(e, registry = nil)
    registry ||= Mapping::DefaultRegistry
    detail = if e.detail
        soap2obj(e.detail, registry) || ""
      else
        ""
      end
    if detail.is_a?(Mapping::SOAPException)
      begin
        raise detail.to_e
      rescue Exception => e2
        detail.set_backtrace(e2)
        raise
      end
    else
      e.detail = detail
      e.set_backtrace(
        if detail.is_a?(Array)
	  detail
        else
          [detail.to_s]
        end
      )
      raise
    end
  end

  def self._obj2soap(obj, registry, type = nil)
    if referent = Thread.current[:SOAPMarshalDataKey][obj.__id__]
      soap_obj = SOAPReference.new
      soap_obj.__setobj__(referent)
      soap_obj
    else
      registry.obj2soap(obj.class, obj, type)
    end
  end

  def self._soap2obj(node, registry)
    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)
      end
    end
    return registry.soap2obj(node.class, node)
  end


  # Allow only (Letter | '_') (Letter | Digit | '-' | '_')* here.
  # Caution: '.' is not allowed here.
  # To follow XML spec., it should be NCName.
  #   (denied chars) => .[0-F][0-F]
  #   ex. a.b => a.2eb
  #
  def self.name2elename(name)
    name.gsub(/([^a-zA-Z0-9:_\-]+)/n) {
      '.' << $1.unpack('H2' * $1.size).join('.')
    }.gsub(/::/n, '..')
  end

  def self.elename2name(name)
    name.gsub(/\.\./n, '::').gsub(/((?:\.[0-9a-fA-F]{2})+)/n) {
      [$1.delete('.')].pack('H*')
    }
  end

  def self.class_from_name(name)
    if /^[A-Z]/ !~ name
      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
    end
    klass
  end

  def self.class2qname(klass)
    name = if klass.class_variables.include?("@@schema_type")
        klass.class_eval("@@schema_type")
      else
        nil
      end
    namespace = if klass.class_variables.include?("@@schema_ns")
        klass.class_eval("@@schema_ns")
      else
        nil
      end
    XSD::QName.new(namespace, name)
  end

  def self.class2element(klass)
    type = Mapping.class2qname(klass)
    type.name ||= Mapping.name2elename(klass.name)
    type.namespace ||= RubyCustomTypeNamespace
    type
  end

  def self.obj2element(obj)
    name = namespace = nil
    ivars = obj.instance_variables
    if ivars.include?("@schema_type")
      name = obj.instance_eval("@schema_type")
    end
    if ivars.include?("@schema_ns")
      namespace = obj.instance_eval("@schema_ns")
    end
    if !name or !namespace
      class2qname(obj.class)
    else
      XSD::QName.new(namespace, name)
    end
  end

  class << Mapping
  private
    def add_md_ary(md_ary, ary, indices, registry)
      for idx in 0..(ary.size - 1)
        if ary[idx].is_a?(Array)
          add_md_ary(md_ary, ary[idx], indices + [idx], registry)
        else
          md_ary[*(indices + [idx])] = _obj2soap(ary[idx], registry)
        end
      end
    end
  end
end


end