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

root/trunk/lib/soap/rpc/proxy.rb

Revision 2011, 16.6 kB (checked in by nahi, 1 year ago)
  • merged from 1_5 branch.
    • enabled Sriver#generate_explicit_type for literal services.
    • test added.
    • removed generated files.
  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 # SOAP4R - RPC Proxy library.
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 'soap/soap'
10 require 'soap/processor'
11 require 'soap/mapping'
12 require 'soap/mapping/literalregistry'
13 require 'soap/rpc/rpc'
14 require 'soap/rpc/element'
15 require 'soap/header/handlerset'
16 require 'soap/filter'
17 require 'soap/streamHandler'
18 require 'soap/mimemessage'
19
20
21 module SOAP
22 module RPC
23
24
25 class Proxy
26   include SOAP
27
28 public
29
30   attr_accessor :soapaction
31   attr_accessor :mandatorycharset
32   attr_accessor :allow_unqualified_element
33   attr_accessor :default_encodingstyle
34   attr_accessor :generate_explicit_type
35   attr_accessor :use_default_namespace
36   attr_accessor :return_response_as_xml
37   attr_reader :headerhandler
38   attr_reader :filterchain
39   attr_reader :streamhandler
40
41   attr_accessor :mapping_registry
42   attr_accessor :literal_mapping_registry
43
44   attr_reader :operation
45
46   def initialize(endpoint_url, soapaction, options)
47     @endpoint_url = endpoint_url
48     @soapaction = soapaction
49     @options = options
50     @protocol_option = options["protocol"] ||= ::SOAP::Property.new
51     initialize_streamhandler(@protocol_option)
52     @operation = {}
53     @operation_by_qname = {}
54     @operation_by_soapaction = {}
55     @mandatorycharset = nil
56     # TODO: set to false by default or drop thie option in 1.6.0
57     @allow_unqualified_element = true
58     @default_encodingstyle = nil
59     @generate_explicit_type = nil
60     @use_default_namespace = false
61     @return_response_as_xml = false
62     @headerhandler = Header::HandlerSet.new
63     @filterchain = Filter::FilterChain.new
64     @mapping_registry = nil
65     @literal_mapping_registry = ::SOAP::Mapping::LiteralRegistry.new
66   end
67
68   def inspect
69     "#<#{self.class}:#{@endpoint_url}>"
70   end
71
72   def endpoint_url
73     @endpoint_url
74   end
75
76   def endpoint_url=(endpoint_url)
77     @endpoint_url = endpoint_url
78     reset_stream
79   end
80
81   def reset_stream
82     @streamhandler.reset(@endpoint_url)
83   end
84
85   def set_wiredump_file_base(wiredump_file_base)
86     @streamhandler.wiredump_file_base = wiredump_file_base
87   end
88
89   def test_loopback_response
90     @streamhandler.test_loopback_response
91   end
92
93   def add_rpc_operation(qname, soapaction, name, param_def, opt = {})
94     ensure_styleuse_option(opt, :rpc, :encoded)
95     opt[:request_qname] = qname
96     op = Operation.new(soapaction, param_def, opt)
97     assign_operation(name, qname, soapaction, op)
98   end
99
100   def add_document_operation(soapaction, name, param_def, opt = {})
101     ensure_styleuse_option(opt, :document, :literal)
102     op = Operation.new(soapaction, param_def, opt)
103     assign_operation(name, nil, soapaction, op)
104   end
105
106   # add_method is for shortcut of typical rpc/encoded method definition.
107   alias add_method add_rpc_operation
108   alias add_rpc_method add_rpc_operation
109   alias add_document_method add_document_operation
110
111   def invoke(req_header, req_body, opt = nil)
112     opt ||= create_encoding_opt
113     env = route(req_header, req_body, opt, opt)
114     if @return_response_as_xml
115       opt[:response_as_xml]
116     else
117       env
118     end
119   end
120
121   def call(name, *params)
122     # name must be used only for lookup
123     op_info = lookup_operation(name)
124     mapping_opt = create_mapping_opt
125     req_header = create_request_header
126     req_body = SOAPBody.new(
127       op_info.request_body(params, @mapping_registry,
128         @literal_mapping_registry, mapping_opt)
129     )
130     reqopt = create_encoding_opt(
131       :soapaction => op_info.soapaction || @soapaction,
132       :envelopenamespace => @options["soap.envelope.requestnamespace"],
133       :default_encodingstyle =>
134         @default_encodingstyle || op_info.request_default_encodingstyle,
135       :use_default_namespace =>
136         op_info.use_default_namespace || @use_default_namespace
137     )
138     resopt = create_encoding_opt(
139       :envelopenamespace => @options["soap.envelope.responsenamespace"],
140       :default_encodingstyle =>
141         @default_encodingstyle || op_info.response_default_encodingstyle
142     )
143     if reqopt[:generate_explicit_type].nil?
144       reqopt[:generate_explicit_type] = (op_info.request_use == :encoded)
145     end
146     if resopt[:generate_explicit_type].nil?
147       resopt[:generate_explicit_type] = (op_info.response_use == :encoded)
148     end
149     env = route(req_header, req_body, reqopt, resopt)
150     if op_info.response_use.nil?
151       return nil
152     end
153     raise EmptyResponseError unless env
154     receive_headers(env.header)
155     begin
156       check_fault(env.body)
157     rescue ::SOAP::FaultError => e
158       op_info.raise_fault(e, @mapping_registry, @literal_mapping_registry)
159     end
160     if @return_response_as_xml
161       resopt[:response_as_xml]
162     else
163       op_info.response_obj(env.body, @mapping_registry,
164         @literal_mapping_registry, mapping_opt)
165     end
166   end
167
168   def route(req_header, req_body, reqopt, resopt)
169     req_env = ::SOAP::SOAPEnvelope.new(req_header, req_body)
170     unless reqopt[:envelopenamespace].nil?
171       set_envelopenamespace(req_env, reqopt[:envelopenamespace])
172     end
173     reqopt[:external_content] = nil
174     conn_data = marshal(req_env, reqopt)
175     if ext = reqopt[:external_content]
176       mime = MIMEMessage.new
177       ext.each do |k, v|
178         mime.add_attachment(v.data)
179       end
180       mime.add_part(conn_data.send_string + "\r\n")
181       mime.close
182       conn_data.send_string = mime.content_str
183       conn_data.send_contenttype = mime.headers['content-type'].str
184     end
185     conn_data.soapaction = reqopt[:soapaction]
186     conn_data = @streamhandler.send(@endpoint_url, conn_data)
187     if conn_data.receive_string.empty?
188       return nil
189     end
190     unmarshal(conn_data, resopt)
191   end
192
193   def check_fault(body)
194     if body.fault
195       raise SOAP::FaultError.new(body.fault)
196     end
197   end
198
199 private
200
201   def ensure_styleuse_option(opt, style, use)
202     if opt[:request_style] || opt[:response_style] || opt[:request_use] || opt[:response_use]
203       # do not edit
204     else
205       opt[:request_style] ||= style
206       opt[:response_style] ||= style
207       opt[:request_use] ||= use
208       opt[:response_use] ||= use
209     end
210   end
211
212   def initialize_streamhandler(options)
213     value = options["streamhandler"]
214     if value and !value.empty?
215       factory = Property::Util.const_from_name(value)
216     else
217       factory = HTTPStreamHandler
218     end
219     @streamhandler = factory.create(options)
220     options.add_hook("streamhandler") do |key, value|
221       @streamhandler.reset
222       if value.respond_to?(:create)
223         factory = value
224       elsif value and !value.to_str.empty?
225         factory = Property::Util.const_from_name(value.to_str)
226       else
227         factory = HTTPStreamHandler
228       end
229       options.unlock(true)
230       @streamhandler = factory.create(options)
231     end
232   end
233
234   def set_envelopenamespace(env, namespace)
235     env.elename = XSD::QName.new(namespace, env.elename.name)
236     if env.header
237       env.header.elename = XSD::QName.new(namespace, env.header.elename.name)
238     end
239     if env.body
240       env.body.elename = XSD::QName.new(namespace, env.body.elename.name)
241     end
242   end
243
244   def create_request_header
245     header = ::SOAP::SOAPHeader.new
246     items = @headerhandler.on_outbound(header)
247     items.each do |item|
248       header.add(item.elename.name, item)
249     end
250     header
251   end
252
253   def receive_headers(header)
254     @headerhandler.on_inbound(header) if header
255   end
256
257   def marshal(env, opt)
258     @filterchain.each do |filter|
259       env = filter.on_outbound(env, opt)
260       break unless env
261     end
262     send_string = Processor.marshal(env, opt)
263     StreamHandler::ConnectionData.new(send_string)
264   end
265
266   def unmarshal(conn_data, opt)
267     contenttype = conn_data.receive_contenttype
268     xml = nil
269     if /#{MIMEMessage::MultipartContentType}/i =~ contenttype
270       opt[:external_content] = {}
271       mime = MIMEMessage.parse("Content-Type: " + contenttype,
272         conn_data.receive_string)
273       mime.parts.each do |part|
274         value = Attachment.new(part.content)
275         value.contentid = part.contentid
276         obj = SOAPAttachment.new(value)
277         opt[:external_content][value.contentid] = obj if value.contentid
278       end
279       opt[:charset] = @mandatorycharset ||
280         StreamHandler.parse_media_type(mime.root.headers['content-type'].str)
281       xml = mime.root.content
282     else
283       opt[:charset] = @mandatorycharset ||
284         ::SOAP::StreamHandler.parse_media_type(contenttype)
285       xml = conn_data.receive_string
286     end
287     @filterchain.reverse_each do |filter|
288       xml = filter.on_inbound(xml, opt)
289       break unless xml
290     end
291     env = Processor.unmarshal(xml, opt)
292     if @return_response_as_xml
293       opt[:response_as_xml] = xml
294     end
295     unless env.is_a?(::SOAP::SOAPEnvelope)
296       raise ResponseFormatError.new("response is not a SOAP envelope: #{env}")
297     end
298     env
299   end
300
301   def create_encoding_opt(hash = nil)
302     opt = {}
303     opt[:default_encodingstyle] = @default_encodingstyle
304     opt[:allow_unqualified_element] = @allow_unqualified_element
305     opt[:generate_explicit_type] = @generate_explicit_type
306     opt[:no_indent] = @options["soap.envelope.no_indent"]
307     opt[:use_numeric_character_reference] =
308       @options["soap.envelope.use_numeric_character_reference"]
309     opt.update(hash) if hash
310     opt
311   end
312
313   def create_mapping_opt(hash = nil)
314     opt = {
315       :external_ces => @options["soap.mapping.external_ces"]
316     }
317     opt.update(hash) if hash
318     opt
319   end
320
321   def assign_operation(name, qname, soapaction, op)
322     assigned = false
323     if name and !name.empty?
324       @operation[name] = op
325       assigned = true
326     end
327     if qname
328       @operation_by_qname[qname] = op
329       assigned = true
330     end
331     if soapaction and !soapaction.empty?
332       @operation_by_soapaction[soapaction] = op
333       assigned = true
334     end
335     unless assigned
336       raise MethodDefinitionError.new("cannot assign operation")
337     end
338   end
339
340   def lookup_operation(name_or_qname_or_soapaction)
341     if op = @operation[name_or_qname_or_soapaction]
342       return op
343     end
344     if op = @operation_by_qname[name_or_qname_or_soapaction]
345       return op
346     end
347     if op = @operation_by_soapaction[name_or_qname_or_soapaction]
348       return op
349     end
350     raise MethodDefinitionError.new(
351       "operation: #{name_or_qname_or_soapaction} not supported")
352   end
353
354   class Operation
355     attr_reader :soapaction
356     attr_reader :request_style
357     attr_reader :response_style
358     attr_reader :request_use
359     attr_reader :response_use
360     attr_reader :use_default_namespace
361
362     def initialize(soapaction, param_def, opt)
363       @soapaction = soapaction
364       @request_style = opt[:request_style]
365       @response_style = opt[:response_style]
366       @request_use = opt[:request_use]
367       @response_use = opt[:response_use]
368       @use_default_namespace =
369         opt[:use_default_namespace] || opt[:elementformdefault]
370       if opt.key?(:elementformdefault)
371         warn("option :elementformdefault is deprecated.  use :use_default_namespace instead")
372       end
373       check_style(@request_style)
374       check_style(@response_style)
375       check_use(@request_use)
376       check_use(@response_use)
377       if @request_style == :rpc
378         @rpc_request_qname = opt[:request_qname]
379         if @rpc_request_qname.nil?
380           raise MethodDefinitionError.new("rpc_request_qname must be given")
381         end
382         @rpc_method_factory =
383           RPC::SOAPMethodRequest.new(@rpc_request_qname, param_def, @soapaction)
384       else
385         @doc_request_qnames = []
386         @doc_response_qnames = []
387         param_def.each do |param|
388           param = MethodDef.to_param(param)
389           case param.io_type
390           when SOAPMethod::IN
391             @doc_request_qnames << param.qname
392           when SOAPMethod::OUT
393             @doc_response_qnames << param.qname
394           else
395             raise MethodDefinitionError.new(
396               "illegal inout definition for document style: #{param.io_type}")
397           end
398         end
399       end
400     end
401
402     def request_default_encodingstyle
403       (@request_use == :encoded) ? EncodingNamespace : LiteralNamespace
404     end
405
406     def response_default_encodingstyle
407       (@response_use == :encoded) ? EncodingNamespace : LiteralNamespace
408     end
409
410     def request_body(values, mapping_registry, literal_mapping_registry, opt)
411       if @request_style == :rpc
412         request_rpc(values, mapping_registry, literal_mapping_registry, opt)
413       else
414         request_doc(values, mapping_registry, literal_mapping_registry, opt)
415       end
416     end
417
418     def response_obj(body, mapping_registry, literal_mapping_registry, opt)
419       if @response_style == :rpc
420         response_rpc(body, mapping_registry, literal_mapping_registry, opt)
421       else
422         unique_result_for_one_element_array(
423           response_doc(body, mapping_registry, literal_mapping_registry, opt))
424       end
425     end
426
427     def raise_fault(e, mapping_registry, literal_mapping_registry)
428       if @response_style == :rpc
429         Mapping.fault2exception(e, mapping_registry)
430       else
431         Mapping.fault2exception(e, literal_mapping_registry)
432       end
433     end
434
435   private
436
437     # nil for [] / 1 for [1] / [1, 2] for [1, 2]
438     def unique_result_for_one_element_array(ary)
439       ary.size <= 1 ? ary[0] : ary
440     end
441
442     def check_style(style)
443       unless [:rpc, :document].include?(style)
444         raise MethodDefinitionError.new("unknown style: #{style}")
445       end
446     end
447
448     # nil means oneway
449     def check_use(use)
450       unless [:encoded, :literal, nil].include?(use)
451         raise MethodDefinitionError.new("unknown use: #{use}")
452       end
453     end
454
455     def request_rpc(values, mapping_registry, literal_mapping_registry, opt)
456       if @request_use == :encoded
457         request_rpc_enc(values, mapping_registry, opt)
458       else
459         request_rpc_lit(values, literal_mapping_registry, opt)
460       end
461     end
462
463     def request_doc(values, mapping_registry, literal_mapping_registry, opt)
464       if @request_use == :encoded
465         request_doc_enc(values, mapping_registry, opt)
466       else
467         request_doc_lit(values, literal_mapping_registry, opt)
468       end
469     end
470
471     def request_rpc_enc(values, mapping_registry, opt)
472       method = @rpc_method_factory.dup
473       names = method.input_params
474       types = method.input_param_types
475       ary = Mapping.objs2soap(values, mapping_registry, types, opt)
476       soap = {}
477       0.upto(ary.length - 1) do |idx|
478         soap[names[idx]] = ary[idx]
479       end
480       method.set_param(soap)
481       method
482     end
483
484     def request_rpc_lit(values, mapping_registry, opt)
485       method = @rpc_method_factory.dup
486       names = method.input_params
487       types = method.get_paramtypes(names)
488       params = {}
489       idx = 0
490       names.each do |name|
491         params[name] = Mapping.obj2soap(values[idx], mapping_registry,
492           types[idx], opt)
493         params[name].elename = XSD::QName.new(nil, name)
494         idx += 1
495       end
496       method.set_param(params)
497       method
498     end
499
500     def request_doc_enc(values, mapping_registry, opt)
501       (0...values.size).collect { |idx|
502         ele = Mapping.obj2soap(values[idx], mapping_registry, nil, opt)
503         ele.elename = @doc_request_qnames[idx]
504         ele
505       }
506     end
507
508     def request_doc_lit(values, mapping_registry, opt)
509       (0...values.size).collect { |idx|
510         ele = Mapping.obj2soap(values[idx], mapping_registry,
511           @doc_request_qnames[idx], opt)
512         ele.encodingstyle = LiteralNamespace
513         ele
514       }
515     end
516
517     def response_rpc(body, mapping_registry, literal_mapping_registry, opt)
518       if @response_use == :encoded
519         response_rpc_enc(body, mapping_registry, opt)
520       else
521         response_rpc_lit(body, literal_mapping_registry, opt)
522       end
523     end
524
525     def response_doc(body, mapping_registry, literal_mapping_registry, opt)
526       if @response_use == :encoded
527         response_doc_enc(body, mapping_registry, opt)
528       else
529         response_doc_lit(body, literal_mapping_registry, opt)
530       end
531     end
532
533     def response_rpc_enc(body, mapping_registry, opt)
534       ret = nil
535       if body.response
536         ret = Mapping.soap2obj(body.response, mapping_registry,
537           @rpc_method_factory.retval_class_name, opt)
538       end
539       if body.outparams
540         outparams = body.outparams.collect { |outparam|
541           Mapping.soap2obj(outparam, mapping_registry, nil, opt)
542         }
543         [ret].concat(outparams)
544       else
545         ret
546       end
547     end
548
549     def response_rpc_lit(body, mapping_registry, opt)
550       body.root_node.collect { |key, value|
551         Mapping.soap2obj(value, mapping_registry,
552           @rpc_method_factory.retval_class_name, opt)
553       }
554     end
555
556     def response_doc_enc(body, mapping_registry, opt)
557       body.collect { |key, value|
558         Mapping.soap2obj(value, mapping_registry, nil, opt)
559       }
560     end
561
562     def response_doc_lit(body, mapping_registry, opt)
563       body.collect { |key, value|
564         Mapping.soap2obj(value, mapping_registry)
565       }
566     end
567   end
568 end
569
570
571 end
572 end
Note: See TracBrowser for help on using the browser.