source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/soap/rpc/router.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 17.7 KB
Line 
1# SOAP4R - RPC Routing library
2# Copyright (C) 2001, 2002, 2004, 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
9require 'soap/soap'
10require 'soap/processor'
11require 'soap/mapping'
12require 'soap/mapping/wsdlliteralregistry'
13require 'soap/rpc/rpc'
14require 'soap/rpc/element'
15require 'soap/streamHandler'
16require 'soap/mimemessage'
17require 'soap/header/handlerset'
18
19
20module SOAP
21module RPC
22
23
24class Router
25 include SOAP
26
27 attr_reader :actor
28 attr_accessor :mapping_registry
29 attr_accessor :literal_mapping_registry
30 attr_accessor :generate_explicit_type
31 attr_accessor :external_ces
32
33 def initialize(actor)
34 @actor = actor
35 @mapping_registry = nil
36 @headerhandler = Header::HandlerSet.new
37 @literal_mapping_registry = ::SOAP::Mapping::WSDLLiteralRegistry.new
38 @generate_explicit_type = true
39 @external_ces = nil
40 @operation_by_soapaction = {}
41 @operation_by_qname = {}
42 @headerhandlerfactory = []
43 end
44
45 ###
46 ## header handler interface
47 #
48 def add_request_headerhandler(factory)
49 unless factory.respond_to?(:create)
50 raise TypeError.new("factory must respond to 'create'")
51 end
52 @headerhandlerfactory << factory
53 end
54
55 def add_headerhandler(handler)
56 @headerhandler.add(handler)
57 end
58
59 ###
60 ## servant definition interface
61 #
62 def add_rpc_request_servant(factory, namespace)
63 unless factory.respond_to?(:create)
64 raise TypeError.new("factory must respond to 'create'")
65 end
66 obj = factory.create # a dummy instance for introspection
67 ::SOAP::RPC.defined_methods(obj).each do |name|
68 begin
69 qname = XSD::QName.new(namespace, name)
70 param_def = ::SOAP::RPC::SOAPMethod.derive_rpc_param_def(obj, name)
71 opt = create_styleuse_option(:rpc, :encoded)
72 add_rpc_request_operation(factory, qname, nil, name, param_def, opt)
73 rescue SOAP::RPC::MethodDefinitionError => e
74 p e if $DEBUG
75 end
76 end
77 end
78
79 def add_rpc_servant(obj, namespace)
80 ::SOAP::RPC.defined_methods(obj).each do |name|
81 begin
82 qname = XSD::QName.new(namespace, name)
83 param_def = ::SOAP::RPC::SOAPMethod.derive_rpc_param_def(obj, name)
84 opt = create_styleuse_option(:rpc, :encoded)
85 add_rpc_operation(obj, qname, nil, name, param_def, opt)
86 rescue SOAP::RPC::MethodDefinitionError => e
87 p e if $DEBUG
88 end
89 end
90 end
91 alias add_servant add_rpc_servant
92
93 ###
94 ## operation definition interface
95 #
96 def add_rpc_operation(receiver, qname, soapaction, name, param_def, opt = {})
97 ensure_styleuse_option(opt, :rpc, :encoded)
98 opt[:request_qname] = qname
99 op = ApplicationScopeOperation.new(soapaction, receiver, name, param_def,
100 opt)
101 if opt[:request_style] != :rpc
102 raise RPCRoutingError.new("illegal request_style given")
103 end
104 assign_operation(soapaction, qname, op)
105 end
106 alias add_method add_rpc_operation
107 alias add_rpc_method add_rpc_operation
108
109 def add_rpc_request_operation(factory, qname, soapaction, name, param_def, opt = {})
110 ensure_styleuse_option(opt, :rpc, :encoded)
111 opt[:request_qname] = qname
112 op = RequestScopeOperation.new(soapaction, factory, name, param_def, opt)
113 if opt[:request_style] != :rpc
114 raise RPCRoutingError.new("illegal request_style given")
115 end
116 assign_operation(soapaction, qname, op)
117 end
118
119 def add_document_operation(receiver, soapaction, name, param_def, opt = {})
120 #
121 # adopt workaround for doc/lit wrapper method
122 # (you should consider to simply use rpc/lit service)
123 #
124 #unless soapaction
125 # raise RPCRoutingError.new("soapaction is a must for document method")
126 #end
127 ensure_styleuse_option(opt, :document, :literal)
128 op = ApplicationScopeOperation.new(soapaction, receiver, name, param_def,
129 opt)
130 if opt[:request_style] != :document
131 raise RPCRoutingError.new("illegal request_style given")
132 end
133 assign_operation(soapaction, first_input_part_qname(param_def), op)
134 end
135 alias add_document_method add_document_operation
136
137 def add_document_request_operation(factory, soapaction, name, param_def, opt = {})
138 #
139 # adopt workaround for doc/lit wrapper method
140 # (you should consider to simply use rpc/lit service)
141 #
142 #unless soapaction
143 # raise RPCRoutingError.new("soapaction is a must for document method")
144 #end
145 ensure_styleuse_option(opt, :document, :literal)
146 op = RequestScopeOperation.new(soapaction, receiver, name, param_def, opt)
147 if opt[:request_style] != :document
148 raise RPCRoutingError.new("illegal request_style given")
149 end
150 assign_operation(soapaction, first_input_part_qname(param_def), op)
151 end
152
153 def route(conn_data)
154 # we cannot set request_default_encodingsyle before parsing the content.
155 env = unmarshal(conn_data)
156 if env.nil?
157 raise ArgumentError.new("illegal SOAP marshal format")
158 end
159 op = lookup_operation(conn_data.soapaction, env.body)
160 headerhandler = @headerhandler.dup
161 @headerhandlerfactory.each do |f|
162 headerhandler.add(f.create)
163 end
164 receive_headers(headerhandler, env.header)
165 soap_response = default_encodingstyle = nil
166 begin
167 soap_response =
168 op.call(env.body, @mapping_registry, @literal_mapping_registry,
169 create_mapping_opt)
170 default_encodingstyle = op.response_default_encodingstyle
171 rescue Exception
172 soap_response = fault($!)
173 default_encodingstyle = nil
174 end
175 conn_data.is_fault = true if soap_response.is_a?(SOAPFault)
176 header = call_headers(headerhandler)
177 body = SOAPBody.new(soap_response)
178 env = SOAPEnvelope.new(header, body)
179 marshal(conn_data, env, default_encodingstyle)
180 end
181
182 # Create fault response string.
183 def create_fault_response(e)
184 env = SOAPEnvelope.new(SOAPHeader.new, SOAPBody.new(fault(e)))
185 opt = {}
186 opt[:external_content] = nil
187 response_string = Processor.marshal(env, opt)
188 conn_data = StreamHandler::ConnectionData.new(response_string)
189 conn_data.is_fault = true
190 if ext = opt[:external_content]
191 mimeize(conn_data, ext)
192 end
193 conn_data
194 end
195
196private
197
198 def first_input_part_qname(param_def)
199 param_def.each do |inout, paramname, typeinfo|
200 if inout == SOAPMethod::IN
201 klass, nsdef, namedef = typeinfo
202 return XSD::QName.new(nsdef, namedef)
203 end
204 end
205 nil
206 end
207
208 def create_styleuse_option(style, use)
209 opt = {}
210 opt[:request_style] = opt[:response_style] = style
211 opt[:request_use] = opt[:response_use] = use
212 opt
213 end
214
215 def ensure_styleuse_option(opt, style, use)
216 opt[:request_style] ||= style
217 opt[:response_style] ||= style
218 opt[:request_use] ||= use
219 opt[:response_use] ||= use
220 end
221
222 def assign_operation(soapaction, qname, op)
223 assigned = false
224 if soapaction and !soapaction.empty?
225 @operation_by_soapaction[soapaction] = op
226 assigned = true
227 end
228 if qname
229 @operation_by_qname[qname] = op
230 assigned = true
231 end
232 unless assigned
233 raise RPCRoutingError.new("cannot assign operation")
234 end
235 end
236
237 def lookup_operation(soapaction, body)
238 if op = @operation_by_soapaction[soapaction]
239 return op
240 end
241 qname = body.root_node.elename
242 if op = @operation_by_qname[qname]
243 return op
244 end
245 if soapaction
246 raise RPCRoutingError.new(
247 "operation: #{soapaction} #{qname} not supported")
248 else
249 raise RPCRoutingError.new("operation: #{qname} not supported")
250 end
251 end
252
253 def call_headers(headerhandler)
254 headers = headerhandler.on_outbound
255 if headers.empty?
256 nil
257 else
258 h = ::SOAP::SOAPHeader.new
259 headers.each do |header|
260 h.add(header.elename.name, header)
261 end
262 h
263 end
264 end
265
266 def receive_headers(headerhandler, headers)
267 headerhandler.on_inbound(headers) if headers
268 end
269
270 def unmarshal(conn_data)
271 opt = {}
272 contenttype = conn_data.receive_contenttype
273 if /#{MIMEMessage::MultipartContentType}/i =~ contenttype
274 opt[:external_content] = {}
275 mime = MIMEMessage.parse("Content-Type: " + contenttype,
276 conn_data.receive_string)
277 mime.parts.each do |part|
278 value = Attachment.new(part.content)
279 value.contentid = part.contentid
280 obj = SOAPAttachment.new(value)
281 opt[:external_content][value.contentid] = obj if value.contentid
282 end
283 opt[:charset] =
284 StreamHandler.parse_media_type(mime.root.headers['content-type'].str)
285 env = Processor.unmarshal(mime.root.content, opt)
286 else
287 opt[:charset] = ::SOAP::StreamHandler.parse_media_type(contenttype)
288 env = Processor.unmarshal(conn_data.receive_string, opt)
289 end
290 charset = opt[:charset]
291 conn_data.send_contenttype = "text/xml; charset=\"#{charset}\""
292 env
293 end
294
295 def marshal(conn_data, env, default_encodingstyle = nil)
296 opt = {}
297 opt[:external_content] = nil
298 opt[:default_encodingstyle] = default_encodingstyle
299 opt[:generate_explicit_type] = @generate_explicit_type
300 response_string = Processor.marshal(env, opt)
301 conn_data.send_string = response_string
302 if ext = opt[:external_content]
303 mimeize(conn_data, ext)
304 end
305 conn_data
306 end
307
308 def mimeize(conn_data, ext)
309 mime = MIMEMessage.new
310 ext.each do |k, v|
311 mime.add_attachment(v.data)
312 end
313 mime.add_part(conn_data.send_string + "\r\n")
314 mime.close
315 conn_data.send_string = mime.content_str
316 conn_data.send_contenttype = mime.headers['content-type'].str
317 conn_data
318 end
319
320 # Create fault response.
321 def fault(e)
322 detail = Mapping::SOAPException.new(e)
323 SOAPFault.new(
324 SOAPString.new('Server'),
325 SOAPString.new(e.to_s),
326 SOAPString.new(@actor),
327 Mapping.obj2soap(detail, @mapping_registry))
328 end
329
330 def create_mapping_opt
331 { :external_ces => @external_ces }
332 end
333
334 class Operation
335 attr_reader :name
336 attr_reader :soapaction
337 attr_reader :request_style
338 attr_reader :response_style
339 attr_reader :request_use
340 attr_reader :response_use
341
342 def initialize(soapaction, name, param_def, opt)
343 @soapaction = soapaction
344 @name = name
345 @request_style = opt[:request_style]
346 @response_style = opt[:response_style]
347 @request_use = opt[:request_use]
348 @response_use = opt[:response_use]
349 check_style(@request_style)
350 check_style(@response_style)
351 check_use(@request_use)
352 check_use(@response_use)
353 if @response_style == :rpc
354 request_qname = opt[:request_qname] or raise
355 @rpc_method_factory =
356 RPC::SOAPMethodRequest.new(request_qname, param_def, @soapaction)
357 @rpc_response_qname = opt[:response_qname]
358 else
359 @doc_request_qnames = []
360 @doc_request_qualified = []
361 @doc_response_qnames = []
362 @doc_response_qualified = []
363 param_def.each do |inout, paramname, typeinfo, eleinfo|
364 klass, nsdef, namedef = typeinfo
365 qualified = eleinfo
366 case inout
367 when SOAPMethod::IN
368 @doc_request_qnames << XSD::QName.new(nsdef, namedef)
369 @doc_request_qualified << qualified
370 when SOAPMethod::OUT
371 @doc_response_qnames << XSD::QName.new(nsdef, namedef)
372 @doc_response_qualified << qualified
373 else
374 raise ArgumentError.new(
375 "illegal inout definition for document style: #{inout}")
376 end
377 end
378 end
379 end
380
381 def request_default_encodingstyle
382 (@request_use == :encoded) ? EncodingNamespace : LiteralNamespace
383 end
384
385 def response_default_encodingstyle
386 (@response_use == :encoded) ? EncodingNamespace : LiteralNamespace
387 end
388
389 def call(body, mapping_registry, literal_mapping_registry, opt)
390 if @request_style == :rpc
391 values = request_rpc(body, mapping_registry, literal_mapping_registry,
392 opt)
393 else
394 values = request_document(body, mapping_registry,
395 literal_mapping_registry, opt)
396 end
397 result = receiver.method(@name.intern).call(*values)
398 return result if result.is_a?(SOAPFault)
399 if @response_style == :rpc
400 response_rpc(result, mapping_registry, literal_mapping_registry, opt)
401 else
402 response_doc(result, mapping_registry, literal_mapping_registry, opt)
403 end
404 end
405
406 private
407
408 def receiver
409 raise NotImplementedError.new('must be defined in derived class')
410 end
411
412 def request_rpc(body, mapping_registry, literal_mapping_registry, opt)
413 request = body.request
414 unless request.is_a?(SOAPStruct)
415 raise RPCRoutingError.new("not an RPC style")
416 end
417 if @request_use == :encoded
418 request_rpc_enc(request, mapping_registry, opt)
419 else
420 request_rpc_lit(request, literal_mapping_registry, opt)
421 end
422 end
423
424 def request_document(body, mapping_registry, literal_mapping_registry, opt)
425 # ToDo: compare names with @doc_request_qnames
426 if @request_use == :encoded
427 request_doc_enc(body, mapping_registry, opt)
428 else
429 request_doc_lit(body, literal_mapping_registry, opt)
430 end
431 end
432
433 def request_rpc_enc(request, mapping_registry, opt)
434 param = Mapping.soap2obj(request, mapping_registry, nil, opt)
435 request.collect { |key, value|
436 param[key]
437 }
438 end
439
440 def request_rpc_lit(request, mapping_registry, opt)
441 request.collect { |key, value|
442 Mapping.soap2obj(value, mapping_registry, nil, opt)
443 }
444 end
445
446 def request_doc_enc(body, mapping_registry, opt)
447 body.collect { |key, value|
448 Mapping.soap2obj(value, mapping_registry, nil, opt)
449 }
450 end
451
452 def request_doc_lit(body, mapping_registry, opt)
453 body.collect { |key, value|
454 Mapping.soap2obj(value, mapping_registry, nil, opt)
455 }
456 end
457
458 def response_rpc(result, mapping_registry, literal_mapping_registry, opt)
459 if @response_use == :encoded
460 response_rpc_enc(result, mapping_registry, opt)
461 else
462 response_rpc_lit(result, literal_mapping_registry, opt)
463 end
464 end
465
466 def response_doc(result, mapping_registry, literal_mapping_registry, opt)
467 if @doc_response_qnames.size == 1 and !result.is_a?(Array)
468 result = [result]
469 end
470 if result.size != @doc_response_qnames.size
471 raise "required #{@doc_response_qnames.size} responses " +
472 "but #{result.size} given"
473 end
474 if @response_use == :encoded
475 response_doc_enc(result, mapping_registry, opt)
476 else
477 response_doc_lit(result, literal_mapping_registry, opt)
478 end
479 end
480
481 def response_rpc_enc(result, mapping_registry, opt)
482 soap_response =
483 @rpc_method_factory.create_method_response(@rpc_response_qname)
484 if soap_response.have_outparam?
485 unless result.is_a?(Array)
486 raise RPCRoutingError.new("out parameter was not returned")
487 end
488 outparams = {}
489 i = 1
490 soap_response.output_params.each do |outparam|
491 outparams[outparam] = Mapping.obj2soap(result[i], mapping_registry,
492 nil, opt)
493 i += 1
494 end
495 soap_response.set_outparam(outparams)
496 soap_response.retval = Mapping.obj2soap(result[0], mapping_registry,
497 nil, opt)
498 else
499 soap_response.retval = Mapping.obj2soap(result, mapping_registry, nil,
500 opt)
501 end
502 soap_response
503 end
504
505 def response_rpc_lit(result, mapping_registry, opt)
506 soap_response =
507 @rpc_method_factory.create_method_response(@rpc_response_qname)
508 if soap_response.have_outparam?
509 unless result.is_a?(Array)
510 raise RPCRoutingError.new("out parameter was not returned")
511 end
512 outparams = {}
513 i = 1
514 soap_response.output_params.each do |outparam|
515 outparams[outparam] = Mapping.obj2soap(result[i], mapping_registry,
516 XSD::QName.new(nil, outparam), opt)
517 i += 1
518 end
519 soap_response.set_outparam(outparams)
520 soap_response.retval = Mapping.obj2soap(result[0], mapping_registry,
521 XSD::QName.new(nil, soap_response.elename), opt)
522 else
523 soap_response.retval = Mapping.obj2soap(result, mapping_registry,
524 XSD::QName.new(nil, soap_response.elename), opt)
525 end
526 soap_response
527 end
528
529 def response_doc_enc(result, mapping_registry, opt)
530 (0...result.size).collect { |idx|
531 ele = Mapping.obj2soap(result[idx], mapping_registry, nil, opt)
532 ele.elename = @doc_response_qnames[idx]
533 ele
534 }
535 end
536
537 def response_doc_lit(result, mapping_registry, opt)
538 (0...result.size).collect { |idx|
539 ele = Mapping.obj2soap(result[idx], mapping_registry,
540 @doc_response_qnames[idx])
541 ele.encodingstyle = LiteralNamespace
542 if ele.respond_to?(:qualified)
543 ele.qualified = @doc_response_qualified[idx]
544 end
545 ele
546 }
547 end
548
549 def check_style(style)
550 unless [:rpc, :document].include?(style)
551 raise ArgumentError.new("unknown style: #{style}")
552 end
553 end
554
555 def check_use(use)
556 unless [:encoded, :literal].include?(use)
557 raise ArgumentError.new("unknown use: #{use}")
558 end
559 end
560 end
561
562 class ApplicationScopeOperation < Operation
563 def initialize(soapaction, receiver, name, param_def, opt)
564 super(soapaction, name, param_def, opt)
565 @receiver = receiver
566 end
567
568 private
569
570 def receiver
571 @receiver
572 end
573 end
574
575 class RequestScopeOperation < Operation
576 def initialize(soapaction, receiver_factory, name, param_def, opt)
577 super(soapaction, name, param_def, opt)
578 unless receiver_factory.respond_to?(:create)
579 raise TypeError.new("factory must respond to 'create'")
580 end
581 @receiver_factory = receiver_factory
582 end
583
584 private
585
586 def receiver
587 @receiver_factory.create
588 end
589 end
590end
591
592
593end
594end
Note: See TracBrowser for help on using the repository browser.