keeguon-actionwebservice 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. data/CHANGELOG +335 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +381 -0
  4. data/Rakefile +184 -0
  5. data/TODO +32 -0
  6. data/examples/googlesearch/README +143 -0
  7. data/examples/googlesearch/autoloading/google_search_api.rb +51 -0
  8. data/examples/googlesearch/autoloading/google_search_controller.rb +58 -0
  9. data/examples/googlesearch/delegated/google_search_service.rb +109 -0
  10. data/examples/googlesearch/delegated/search_controller.rb +8 -0
  11. data/examples/googlesearch/direct/google_search_api.rb +51 -0
  12. data/examples/googlesearch/direct/search_controller.rb +59 -0
  13. data/examples/metaWeblog/README +17 -0
  14. data/examples/metaWeblog/apis/blogger_api.rb +61 -0
  15. data/examples/metaWeblog/apis/blogger_service.rb +35 -0
  16. data/examples/metaWeblog/apis/meta_weblog_api.rb +68 -0
  17. data/examples/metaWeblog/apis/meta_weblog_service.rb +49 -0
  18. data/examples/metaWeblog/controllers/xmlrpc_controller.rb +17 -0
  19. data/generators/web_service/USAGE +28 -0
  20. data/generators/web_service/templates/api_definition.rb +6 -0
  21. data/generators/web_service/templates/controller.rb +9 -0
  22. data/generators/web_service/templates/functional_test.rb +20 -0
  23. data/generators/web_service/web_service_generator.rb +30 -0
  24. data/lib/action_web_service/acts_as_web_service.rb +26 -0
  25. data/lib/action_web_service/api.rb +298 -0
  26. data/lib/action_web_service/base.rb +39 -0
  27. data/lib/action_web_service/casting.rb +160 -0
  28. data/lib/action_web_service/client/base.rb +29 -0
  29. data/lib/action_web_service/client/soap_client.rb +114 -0
  30. data/lib/action_web_service/client/xmlrpc_client.rb +59 -0
  31. data/lib/action_web_service/client.rb +4 -0
  32. data/lib/action_web_service/container/action_controller_container.rb +94 -0
  33. data/lib/action_web_service/container/delegated_container.rb +87 -0
  34. data/lib/action_web_service/container/direct_container.rb +70 -0
  35. data/lib/action_web_service/container.rb +4 -0
  36. data/lib/action_web_service/dispatcher/abstract.rb +209 -0
  37. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +420 -0
  38. data/lib/action_web_service/dispatcher.rb +3 -0
  39. data/lib/action_web_service/invocation.rb +203 -0
  40. data/lib/action_web_service/protocol/abstract.rb +113 -0
  41. data/lib/action_web_service/protocol/discovery.rb +38 -0
  42. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +243 -0
  43. data/lib/action_web_service/protocol/soap_protocol.rb +181 -0
  44. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +124 -0
  45. data/lib/action_web_service/protocol.rb +5 -0
  46. data/lib/action_web_service/scaffolding.rb +282 -0
  47. data/lib/action_web_service/simple.rb +54 -0
  48. data/lib/action_web_service/string_to_datetime_for_soap.rb +17 -0
  49. data/lib/action_web_service/struct.rb +69 -0
  50. data/lib/action_web_service/support/class_inheritable_options.rb +27 -0
  51. data/lib/action_web_service/support/signature_types.rb +262 -0
  52. data/lib/action_web_service/templates/scaffolds/layout.html.erb +65 -0
  53. data/lib/action_web_service/templates/scaffolds/methods.html.erb +6 -0
  54. data/lib/action_web_service/templates/scaffolds/parameters.html.erb +29 -0
  55. data/lib/action_web_service/templates/scaffolds/result.html.erb +30 -0
  56. data/lib/action_web_service/test_invoke.rb +111 -0
  57. data/lib/action_web_service/version.rb +10 -0
  58. data/lib/action_web_service.rb +61 -0
  59. data/lib/actionwebservice.rb +2 -0
  60. data/setup.rb +1380 -0
  61. data/test/abstract_client.rb +185 -0
  62. data/test/abstract_dispatcher.rb +550 -0
  63. data/test/abstract_unit.rb +44 -0
  64. data/test/api_test.rb +103 -0
  65. data/test/apis/auto_load_api.rb +4 -0
  66. data/test/apis/broken_auto_load_api.rb +3 -0
  67. data/test/base_test.rb +43 -0
  68. data/test/casting_test.rb +96 -0
  69. data/test/client_soap_test.rb +157 -0
  70. data/test/client_xmlrpc_test.rb +155 -0
  71. data/test/container_test.rb +76 -0
  72. data/test/dispatcher_action_controller_soap_test.rb +147 -0
  73. data/test/dispatcher_action_controller_xmlrpc_test.rb +60 -0
  74. data/test/fixtures/db_definitions/mysql.sql +8 -0
  75. data/test/fixtures/db_definitions/sqlite3.sql +8 -0
  76. data/test/fixtures/users.yml +12 -0
  77. data/test/gencov +3 -0
  78. data/test/invocation_test.rb +187 -0
  79. data/test/run +6 -0
  80. data/test/scaffolded_controller_test.rb +148 -0
  81. data/test/struct_test.rb +85 -0
  82. data/test/test_invoke_test.rb +114 -0
  83. metadata +175 -0
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ module ActionWebService # :nodoc:
3
+ module Client # :nodoc:
4
+ class ClientError < StandardError # :nodoc:
5
+ end
6
+
7
+ class Base # :nodoc:
8
+ def initialize(api, endpoint_uri)
9
+ @api = api
10
+ @endpoint_uri = endpoint_uri
11
+ end
12
+
13
+ def method_missing(name, *args) # :nodoc:
14
+ call_name = method_name(name)
15
+ return super(name, *args) if call_name.nil?
16
+ self.perform_invocation(call_name, args)
17
+ end
18
+
19
+ private
20
+ def method_name(name)
21
+ if @api.has_api_method?(name.to_sym)
22
+ name.to_s
23
+ elsif @api.has_public_api_method?(name.to_s)
24
+ @api.api_method_name(name.to_s).to_s
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,114 @@
1
+ # encoding: UTF-8
2
+ require 'soap/rpc/driver'
3
+ require 'uri'
4
+
5
+ module ActionWebService # :nodoc:
6
+ module Client # :nodoc:
7
+
8
+ # Implements SOAP client support (using RPC encoding for the messages).
9
+ #
10
+ # ==== Example Usage
11
+ #
12
+ # class PersonAPI < ActionWebService::API::Base
13
+ # api_method :find_all, :returns => [[Person]]
14
+ # end
15
+ #
16
+ # soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...")
17
+ # persons = soap_client.find_all
18
+ #
19
+ class Soap < Base
20
+ # provides access to the underlying soap driver
21
+ attr_reader :driver
22
+
23
+ # Creates a new web service client using the SOAP RPC protocol.
24
+ #
25
+ # +api+ must be an ActionWebService::API::Base derivative, and
26
+ # +endpoint_uri+ must point at the relevant URL to which protocol requests
27
+ # will be sent with HTTP POST.
28
+ #
29
+ # Valid options:
30
+ # [<tt>:namespace</tt>] If the remote server has used a custom namespace to
31
+ # declare its custom types, you can specify it here. This would
32
+ # be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute
33
+ # in .NET, for example.
34
+ # [<tt>:driver_options</tt>] If you want to supply any custom SOAP RPC driver
35
+ # options, you can provide them as a Hash here
36
+ #
37
+ # The <tt>:driver_options</tt> option can be used to configure the backend SOAP
38
+ # RPC driver. An example of configuring the SOAP backend to do
39
+ # client-certificate authenticated SSL connections to the server:
40
+ #
41
+ # opts = {}
42
+ # opts['protocol.http.ssl_config.verify_mode'] = 'OpenSSL::SSL::VERIFY_PEER'
43
+ # opts['protocol.http.ssl_config.client_cert'] = client_cert_file_path
44
+ # opts['protocol.http.ssl_config.client_key'] = client_key_file_path
45
+ # opts['protocol.http.ssl_config.ca_file'] = ca_cert_file_path
46
+ # client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts)
47
+ def initialize(api, endpoint_uri, options={})
48
+ super(api, endpoint_uri)
49
+ @namespace = options[:namespace] || 'urn:ActionWebService'
50
+ @driver_options = options[:driver_options] || {}
51
+ @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace
52
+ @soap_action_base = options[:soap_action_base]
53
+ @soap_action_base ||= URI.parse(endpoint_uri).path
54
+ @driver = create_soap_rpc_driver(api, endpoint_uri)
55
+ @driver_options.each do |name, value|
56
+ @driver.options[name.to_s] = value
57
+ end
58
+ end
59
+
60
+ protected
61
+ def perform_invocation(method_name, args)
62
+ method = @api.api_methods[method_name.to_sym]
63
+ args = method.cast_expects(args.dup) rescue args
64
+ return_value = @driver.send(method_name, *args)
65
+ method.cast_returns(return_value.dup) rescue return_value
66
+ end
67
+
68
+ def soap_action(method_name)
69
+ "#{@soap_action_base}/#{method_name}"
70
+ end
71
+
72
+ private
73
+ def create_soap_rpc_driver(api, endpoint_uri)
74
+ @protocol.register_api(api)
75
+ driver = SoapDriver.new(endpoint_uri, nil)
76
+ driver.mapping_registry = @protocol.marshaler.registry
77
+ api.api_methods.each do |name, method|
78
+ qname = XSD::QName.new(@namespace, method.public_name)
79
+ action = soap_action(method.public_name)
80
+ expects = method.expects
81
+ returns = method.returns
82
+ param_def = []
83
+ if expects
84
+ expects.each do |type|
85
+ type_binding = @protocol.marshaler.lookup_type(type)
86
+ if SOAP::Version >= "1.5.5"
87
+ param_def << ['in', type.name.to_s, [type_binding.type.type_class.to_s]]
88
+ else
89
+ param_def << ['in', type.name, type_binding.mapping]
90
+ end
91
+ end
92
+ end
93
+ if returns
94
+ type_binding = @protocol.marshaler.lookup_type(returns[0])
95
+ if SOAP::Version >= "1.5.5"
96
+ param_def << ['retval', 'return', [type_binding.type.type_class.to_s]]
97
+ else
98
+ param_def << ['retval', 'return', type_binding.mapping]
99
+ end
100
+ end
101
+ driver.add_method(qname, action, method.name.to_s, param_def)
102
+ end
103
+ driver
104
+ end
105
+
106
+ class SoapDriver < SOAP::RPC::Driver # :nodoc:
107
+ def add_method(qname, soapaction, name, param_def)
108
+ @proxy.add_rpc_method(qname, soapaction, name, param_def)
109
+ add_rpc_method_interface(name, param_def)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+ require 'uri'
3
+ require 'xmlrpc/client'
4
+
5
+ module ActionWebService # :nodoc:
6
+ module Client # :nodoc:
7
+
8
+ # Implements XML-RPC client support
9
+ #
10
+ # ==== Example Usage
11
+ #
12
+ # class BloggerAPI < ActionWebService::API::Base
13
+ # inflect_names false
14
+ # api_method :getRecentPosts, :returns => [[Blog::Post]]
15
+ # end
16
+ #
17
+ # blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger")
18
+ # posts = blog.getRecentPosts
19
+ class XmlRpc < Base
20
+
21
+ # Creates a new web service client using the XML-RPC protocol.
22
+ #
23
+ # +api+ must be an ActionWebService::API::Base derivative, and
24
+ # +endpoint_uri+ must point at the relevant URL to which protocol requests
25
+ # will be sent with HTTP POST.
26
+ #
27
+ # Valid options:
28
+ # [<tt>:handler_name</tt>] If the remote server defines its services inside special
29
+ # handler (the Blogger API uses a <tt>"blogger"</tt> handler name for example),
30
+ # provide it here, or your method calls will fail
31
+ def initialize(api, endpoint_uri, options={})
32
+ @api = api
33
+ @handler_name = options[:handler_name]
34
+ @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
35
+ @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
36
+ end
37
+
38
+ protected
39
+ def perform_invocation(method_name, args)
40
+ method = @api.api_methods[method_name.to_sym]
41
+ if method.expects && method.expects.length != args.length
42
+ raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})")
43
+ end
44
+ args = method.cast_expects(args.dup) rescue args
45
+ if method.expects
46
+ method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) }
47
+ end
48
+ ok, return_value = @client.call2(public_name(method_name), *args)
49
+ return (method.cast_returns(return_value.dup) rescue return_value) if ok
50
+ raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
51
+ end
52
+
53
+ def public_name(method_name)
54
+ public_name = @api.public_api_method_name(method_name)
55
+ @handler_name ? "#{@handler_name}.#{public_name}" : public_name
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: UTF-8
2
+ require 'action_web_service/client/base'
3
+ require 'action_web_service/client/soap_client'
4
+ require 'action_web_service/client/xmlrpc_client'
@@ -0,0 +1,94 @@
1
+ # encoding: UTF-8
2
+ module ActionWebService # :nodoc:
3
+ module Container # :nodoc:
4
+ module ActionController # :nodoc:
5
+ def self.included(base) # :nodoc:
6
+ class << base
7
+ include ClassMethods
8
+ alias_method_chain :inherited, :api
9
+ alias_method_chain :web_service_api, :require
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ # Creates a client for accessing remote web services, using the
15
+ # given +protocol+ to communicate with the +endpoint_uri+.
16
+ #
17
+ # ==== Example
18
+ #
19
+ # class MyController < ActionController::Base
20
+ # web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger'
21
+ # end
22
+ #
23
+ # In this example, a protected method named <tt>blogger</tt> will
24
+ # now exist on the controller, and calling it will return the
25
+ # XML-RPC client object for working with that remote service.
26
+ #
27
+ # +options+ is the set of protocol client specific options (see
28
+ # a protocol client class for details).
29
+ #
30
+ # If your API definition does not exist on the load path with the
31
+ # correct rules for it to be found using +name+, you can pass in
32
+ # the API definition class via +options+, using a key of <tt>:api</tt>
33
+ def web_client_api(name, protocol, endpoint_uri, options={})
34
+ unless method_defined?(name)
35
+ api_klass = options.delete(:api) || require_web_service_api(name)
36
+ class_eval do
37
+ define_method(name) do
38
+ create_web_service_client(api_klass, protocol, endpoint_uri, options)
39
+ end
40
+ protected name
41
+ end
42
+ end
43
+ end
44
+
45
+ def web_service_api_with_require(definition=nil) # :nodoc:
46
+ return web_service_api_without_require if definition.nil?
47
+ case definition
48
+ when String, Symbol
49
+ klass = require_web_service_api(definition)
50
+ else
51
+ klass = definition
52
+ end
53
+ web_service_api_without_require(klass)
54
+ end
55
+
56
+ def require_web_service_api(name) # :nodoc:
57
+ case name
58
+ when String, Symbol
59
+ file_name = name.to_s.underscore + "_api"
60
+ class_name = file_name.camelize
61
+ class_names = [class_name, class_name.sub(/Api$/, 'API')]
62
+ begin
63
+ require_dependency(file_name)
64
+ rescue LoadError => load_error
65
+ requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
66
+ msg = requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}"
67
+ raise LoadError.new(msg).copy_blame!(load_error)
68
+ end
69
+ klass = nil
70
+ class_names.each do |name|
71
+ klass = name.constantize rescue nil
72
+ break unless klass.nil?
73
+ end
74
+ unless klass
75
+ raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found")
76
+ end
77
+ klass
78
+ else
79
+ raise(ArgumentError, "expected String or Symbol argument")
80
+ end
81
+ end
82
+
83
+ private
84
+ def inherited_with_api(child)
85
+ inherited_without_api(child)
86
+ begin child.web_service_api(child.controller_path)
87
+ rescue MissingSourceFile => e
88
+ raise unless e.is_missing?("apis/#{child.controller_path}_api")
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: UTF-8
2
+ module ActionWebService # :nodoc:
3
+ module Container # :nodoc:
4
+ module Delegated # :nodoc:
5
+ class ContainerError < ActionWebServiceError # :nodoc:
6
+ end
7
+
8
+ def self.included(base) # :nodoc:
9
+ base.extend(ClassMethods)
10
+ base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+ # Declares a web service that will provide access to the API of the given
15
+ # +object+. +object+ must be an ActionWebService::Base derivative.
16
+ #
17
+ # Web service object creation can either be _immediate_, where the object
18
+ # instance is given at class definition time, or _deferred_, where
19
+ # object instantiation is delayed until request time.
20
+ #
21
+ # ==== Immediate web service object example
22
+ #
23
+ # class ApiController < ApplicationController
24
+ # web_service_dispatching_mode :delegated
25
+ #
26
+ # web_service :person, PersonService.new
27
+ # end
28
+ #
29
+ # For deferred instantiation, a block should be given instead of an
30
+ # object instance. This block will be executed in controller instance
31
+ # context, so it can rely on controller instance variables being present.
32
+ #
33
+ # ==== Deferred web service object example
34
+ #
35
+ # class ApiController < ApplicationController
36
+ # web_service_dispatching_mode :delegated
37
+ #
38
+ # web_service(:person) { PersonService.new(request.env) }
39
+ # end
40
+ def web_service(name, object=nil, &block)
41
+ if (object && block_given?) || (object.nil? && block.nil?)
42
+ raise(ContainerError, "either service, or a block must be given")
43
+ end
44
+ name = name.to_sym
45
+ if block_given?
46
+ info = { name => { :block => block } }
47
+ else
48
+ info = { name => { :object => object } }
49
+ end
50
+ write_inheritable_hash("web_services", info)
51
+ call_web_service_definition_callbacks(self, name, info)
52
+ end
53
+
54
+ # Whether this service contains a service with the given +name+
55
+ def has_web_service?(name)
56
+ web_services.has_key?(name.to_sym)
57
+ end
58
+
59
+ def web_services # :nodoc:
60
+ read_inheritable_attribute("web_services") || {}
61
+ end
62
+
63
+ def add_web_service_definition_callback(&block) # :nodoc:
64
+ write_inheritable_array("web_service_definition_callbacks", [block])
65
+ end
66
+
67
+ private
68
+ def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
69
+ (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
70
+ block.call(container_class, web_service_name, service_info)
71
+ end
72
+ end
73
+ end
74
+
75
+ module InstanceMethods # :nodoc:
76
+ def web_service_object(web_service_name)
77
+ info = self.class.web_services[web_service_name.to_sym]
78
+ unless info
79
+ raise(ContainerError, "no such web service '#{web_service_name}'")
80
+ end
81
+ service = info[:block]
82
+ service ? self.instance_eval(&service) : info[:object]
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: UTF-8
2
+ module ActionWebService # :nodoc:
3
+ module Container # :nodoc:
4
+ module Direct # :nodoc:
5
+ class ContainerError < ActionWebServiceError # :nodoc:
6
+ end
7
+
8
+ def self.included(base) # :nodoc:
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ # Attaches ActionWebService API +definition+ to the calling class.
14
+ #
15
+ # Action Controllers can have a default associated API, removing the need
16
+ # to call this method if you follow the Action Web Service naming conventions.
17
+ #
18
+ # A controller with a class name of GoogleSearchController will
19
+ # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
20
+ # API definition class to be named <tt>GoogleSearchAPI</tt> or
21
+ # <tt>GoogleSearchApi</tt>.
22
+ #
23
+ # ==== Service class example
24
+ #
25
+ # class MyService < ActionWebService::Base
26
+ # web_service_api MyAPI
27
+ # end
28
+ #
29
+ # class MyAPI < ActionWebService::API::Base
30
+ # ...
31
+ # end
32
+ #
33
+ # ==== Controller class example
34
+ #
35
+ # class MyController < ActionController::Base
36
+ # web_service_api MyAPI
37
+ # end
38
+ #
39
+ # class MyAPI < ActionWebService::API::Base
40
+ # ...
41
+ # end
42
+ def web_service_api(definition=nil)
43
+ if definition.nil?
44
+ read_inheritable_attribute("web_service_api")
45
+ else
46
+ if definition.is_a?(Symbol)
47
+ raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
48
+ end
49
+ unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
50
+ raise(ContainerError, "#{definition.to_s} is not a valid API definition")
51
+ end
52
+ write_inheritable_attribute("web_service_api", definition)
53
+ call_web_service_api_callbacks(self, definition)
54
+ end
55
+ end
56
+
57
+ def add_web_service_api_callback(&block) # :nodoc:
58
+ write_inheritable_array("web_service_api_callbacks", [block])
59
+ end
60
+
61
+ private
62
+ def call_web_service_api_callbacks(container_class, definition)
63
+ (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
64
+ block.call(container_class, definition)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: UTF-8
2
+ require 'action_web_service/container/direct_container'
3
+ require 'action_web_service/container/delegated_container'
4
+ require 'action_web_service/container/action_controller_container'
@@ -0,0 +1,209 @@
1
+ # encoding: UTF-8
2
+ require 'benchmark'
3
+
4
+ module ActionWebService # :nodoc:
5
+ module Dispatcher # :nodoc:
6
+ class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
7
+ def initialize(*args)
8
+ super
9
+ set_backtrace(caller)
10
+ end
11
+ end
12
+
13
+ def self.included(base) # :nodoc:
14
+ base.class_inheritable_option(:web_service_dispatching_mode, :direct)
15
+ base.class_inheritable_option(:web_service_inflect_type, false)
16
+ base.class_inheritable_option(:web_service_exception_reporting, true)
17
+ base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
18
+ end
19
+
20
+ module InstanceMethods # :nodoc:
21
+ private
22
+ def invoke_web_service_request(protocol_request)
23
+ invocation = web_service_invocation(protocol_request)
24
+ if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
25
+ xmlrpc_multicall_invoke(invocation)
26
+ else
27
+ web_service_invoke(invocation)
28
+ end
29
+ end
30
+
31
+ def web_service_direct_invoke(invocation)
32
+ @method_params = invocation.method_ordered_params
33
+ arity = method(invocation.api_method.name).arity rescue 0
34
+ if arity < 0 || arity > 0
35
+ params = @method_params
36
+ else
37
+ params = []
38
+ end
39
+ web_service_filtered_invoke(invocation, params)
40
+ end
41
+
42
+ def web_service_delegated_invoke(invocation)
43
+ web_service_filtered_invoke(invocation, invocation.method_ordered_params)
44
+ end
45
+
46
+ def web_service_filtered_invoke(invocation, params)
47
+ cancellation_reason = nil
48
+ return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x|
49
+ cancellation_reason = x
50
+ end
51
+ if cancellation_reason
52
+ raise(DispatcherError, "request canceled: #{cancellation_reason}")
53
+ end
54
+ return_value
55
+ end
56
+
57
+ def web_service_invoke(invocation)
58
+ case web_service_dispatching_mode
59
+ when :direct
60
+ return_value = web_service_direct_invoke(invocation)
61
+ when :delegated, :layered
62
+ return_value = web_service_delegated_invoke(invocation)
63
+ end
64
+ web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value)
65
+ end
66
+
67
+ def xmlrpc_multicall_invoke(invocations)
68
+ responses = []
69
+ invocations.each do |invocation|
70
+ if invocation.is_a?(Hash)
71
+ responses << [invocation, nil]
72
+ next
73
+ end
74
+ begin
75
+ case web_service_dispatching_mode
76
+ when :direct
77
+ return_value = web_service_direct_invoke(invocation)
78
+ when :delegated, :layered
79
+ return_value = web_service_delegated_invoke(invocation)
80
+ end
81
+ api_method = invocation.api_method
82
+ if invocation.api.has_api_method?(api_method.name)
83
+ response_type = (api_method.returns ? api_method.returns[0] : nil)
84
+ return_value = api_method.cast_returns(return_value)
85
+ else
86
+ response_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
87
+ end
88
+ responses << [return_value, response_type]
89
+ rescue Exception => e
90
+ responses << [{ 'faultCode' => 3, 'faultString' => e.message }, nil]
91
+ end
92
+ end
93
+ invocation = invocations[0]
94
+ invocation.protocol.encode_multicall_response(responses, invocation.protocol_options)
95
+ end
96
+
97
+ def web_service_invocation(request, level = 0)
98
+ public_method_name = request.method_name
99
+ invocation = Invocation.new
100
+ invocation.protocol = request.protocol
101
+ invocation.protocol_options = request.protocol_options
102
+ invocation.service_name = request.service_name
103
+ if web_service_dispatching_mode == :layered
104
+ case invocation.protocol
105
+ when Protocol::Soap::SoapProtocol
106
+ soap_action = request.protocol_options[:soap_action]
107
+ if soap_action && soap_action =~ /^\/\w+\/(\w+)\//
108
+ invocation.service_name = $1
109
+ end
110
+ when Protocol::XmlRpc::XmlRpcProtocol
111
+ if request.method_name =~ /^([^\.]+)\.(.*)$/
112
+ public_method_name = $2
113
+ invocation.service_name = $1
114
+ end
115
+ end
116
+ end
117
+ if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol
118
+ if public_method_name == 'multicall' && invocation.service_name == 'system'
119
+ if level > 0
120
+ raise(DispatcherError, "Recursive system.multicall invocations not allowed")
121
+ end
122
+ multicall = request.method_params.dup
123
+ unless multicall.is_a?(Array) && multicall[0].is_a?(Array)
124
+ raise(DispatcherError, "Malformed multicall (expected array of Hash elements)")
125
+ end
126
+ multicall = multicall[0]
127
+ return multicall.map do |item|
128
+ raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash)
129
+ raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName')
130
+ method_name = item['methodName']
131
+ params = item.has_key?('params') ? item['params'] : []
132
+ multicall_request = request.dup
133
+ multicall_request.method_name = method_name
134
+ multicall_request.method_params = params
135
+ begin
136
+ web_service_invocation(multicall_request, level + 1)
137
+ rescue Exception => e
138
+ {'faultCode' => 4, 'faultMessage' => e.message}
139
+ end
140
+ end
141
+ end
142
+ end
143
+ case web_service_dispatching_mode
144
+ when :direct
145
+ invocation.api = self.class.web_service_api
146
+ invocation.service = self
147
+ when :delegated, :layered
148
+ invocation.service = web_service_object(invocation.service_name)
149
+ invocation.api = invocation.service.class.web_service_api
150
+ end
151
+ if invocation.api.nil?
152
+ raise(DispatcherError, "no API attached to #{invocation.service.class}")
153
+ end
154
+ invocation.protocol.register_api(invocation.api)
155
+ request.api = invocation.api
156
+ if invocation.api.has_public_api_method?(public_method_name)
157
+ invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
158
+ else
159
+ if invocation.api.default_api_method.nil?
160
+ raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
161
+ else
162
+ invocation.api_method = invocation.api.default_api_method_instance
163
+ end
164
+ end
165
+ if invocation.service.nil?
166
+ raise(DispatcherError, "no service available for service name #{invocation.service_name}")
167
+ end
168
+ unless invocation.service.respond_to?(invocation.api_method.name)
169
+ raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
170
+ end
171
+ request.api_method = invocation.api_method
172
+ begin
173
+ invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
174
+ rescue
175
+ logger.warn "Casting of method parameters failed" unless logger.nil?
176
+ invocation.method_ordered_params = request.method_params
177
+ end
178
+ request.method_params = invocation.method_ordered_params
179
+ invocation.method_named_params = {}
180
+ invocation.api_method.param_names.inject(0) do |m, n|
181
+ invocation.method_named_params[n] = invocation.method_ordered_params[m]
182
+ m + 1
183
+ end
184
+ invocation
185
+ end
186
+
187
+ def web_service_create_response(protocol, protocol_options, api, api_method, return_value)
188
+ if api.has_api_method?(api_method.name)
189
+ return_type = api_method.returns ? api_method.returns[0] : nil
190
+ return_value = api_method.cast_returns(return_value)
191
+ else
192
+ return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
193
+ end
194
+ protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options)
195
+ end
196
+
197
+ class Invocation # :nodoc:
198
+ attr_accessor :protocol
199
+ attr_accessor :protocol_options
200
+ attr_accessor :service_name
201
+ attr_accessor :api
202
+ attr_accessor :api_method
203
+ attr_accessor :method_ordered_params
204
+ attr_accessor :method_named_params
205
+ attr_accessor :service
206
+ end
207
+ end
208
+ end
209
+ end