actionwebservice 0.5.0

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 (54) hide show
  1. data/ChangeLog +47 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +238 -0
  4. data/Rakefile +144 -0
  5. data/TODO +13 -0
  6. data/examples/googlesearch/README +143 -0
  7. data/examples/googlesearch/autoloading/google_search_api.rb +50 -0
  8. data/examples/googlesearch/autoloading/google_search_controller.rb +57 -0
  9. data/examples/googlesearch/delegated/google_search_service.rb +108 -0
  10. data/examples/googlesearch/delegated/search_controller.rb +7 -0
  11. data/examples/googlesearch/direct/google_search_api.rb +50 -0
  12. data/examples/googlesearch/direct/search_controller.rb +58 -0
  13. data/examples/metaWeblog/README +16 -0
  14. data/examples/metaWeblog/blog_controller.rb +127 -0
  15. data/lib/action_web_service.rb +60 -0
  16. data/lib/action_web_service/api.rb +2 -0
  17. data/lib/action_web_service/api/abstract.rb +192 -0
  18. data/lib/action_web_service/api/action_controller.rb +92 -0
  19. data/lib/action_web_service/base.rb +41 -0
  20. data/lib/action_web_service/client.rb +3 -0
  21. data/lib/action_web_service/client/base.rb +39 -0
  22. data/lib/action_web_service/client/soap_client.rb +88 -0
  23. data/lib/action_web_service/client/xmlrpc_client.rb +77 -0
  24. data/lib/action_web_service/container.rb +85 -0
  25. data/lib/action_web_service/dispatcher.rb +2 -0
  26. data/lib/action_web_service/dispatcher/abstract.rb +150 -0
  27. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +299 -0
  28. data/lib/action_web_service/invocation.rb +205 -0
  29. data/lib/action_web_service/protocol.rb +4 -0
  30. data/lib/action_web_service/protocol/abstract.rb +128 -0
  31. data/lib/action_web_service/protocol/registry.rb +55 -0
  32. data/lib/action_web_service/protocol/soap_protocol.rb +484 -0
  33. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +168 -0
  34. data/lib/action_web_service/struct.rb +55 -0
  35. data/lib/action_web_service/support/class_inheritable_options.rb +26 -0
  36. data/lib/action_web_service/support/signature.rb +100 -0
  37. data/setup.rb +1360 -0
  38. data/test/abstract_client.rb +131 -0
  39. data/test/abstract_soap.rb +58 -0
  40. data/test/abstract_unit.rb +9 -0
  41. data/test/api_test.rb +52 -0
  42. data/test/base_test.rb +42 -0
  43. data/test/client_soap_test.rb +93 -0
  44. data/test/client_xmlrpc_test.rb +92 -0
  45. data/test/container_test.rb +53 -0
  46. data/test/dispatcher_action_controller_test.rb +186 -0
  47. data/test/gencov +3 -0
  48. data/test/invocation_test.rb +149 -0
  49. data/test/protocol_registry_test.rb +53 -0
  50. data/test/protocol_soap_test.rb +252 -0
  51. data/test/protocol_xmlrpc_test.rb +147 -0
  52. data/test/run +5 -0
  53. data/test/struct_test.rb +40 -0
  54. metadata +131 -0
@@ -0,0 +1,41 @@
1
+ require 'action_web_service/support/class_inheritable_options'
2
+ require 'action_web_service/support/signature'
3
+
4
+ module ActionWebService # :nodoc:
5
+ class ActionWebServiceError < StandardError # :nodoc:
6
+ end
7
+
8
+ # An Action Web Service object implements a specified API.
9
+ #
10
+ # Used by controllers operating in _Delegated_ dispatching mode.
11
+ #
12
+ # ==== Example
13
+ #
14
+ # class PersonService < ActionWebService::Base
15
+ # web_service_api PersonAPI
16
+ #
17
+ # def find_person(criteria)
18
+ # Person.find_all [...]
19
+ # end
20
+ #
21
+ # def delete_person(id)
22
+ # Person.find_by_id(id).destroy
23
+ # end
24
+ # end
25
+ #
26
+ # class PersonAPI < ActionWebService::API::Base
27
+ # api_method :find_person, :expects => [SearchCriteria], :returns => [[Person]]
28
+ # api_method :delete_person, :expects => [:int]
29
+ # end
30
+ #
31
+ # class SearchCriteria < ActionStruct::Base
32
+ # member :firstname, :string
33
+ # member :lastname, :string
34
+ # member :email, :string
35
+ # end
36
+ class Base
37
+ # Whether to report exceptions back to the caller in the protocol's exception
38
+ # format
39
+ class_inheritable_option :web_service_exception_reporting, true
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ require 'action_web_service/client/base'
2
+ require 'action_web_service/client/soap_client'
3
+ require 'action_web_service/client/xmlrpc_client'
@@ -0,0 +1,39 @@
1
+ module ActionWebService # :nodoc:
2
+ module Client # :nodoc:
3
+ class ClientError < StandardError # :nodoc:
4
+ end
5
+
6
+ class Base # :nodoc:
7
+ def initialize(api, endpoint_uri)
8
+ @api = api
9
+ @endpoint_uri = endpoint_uri
10
+ end
11
+
12
+ def method_missing(name, *args) # :nodoc:
13
+ call_name = method_name(name)
14
+ return super(name, *args) if call_name.nil?
15
+ perform_invocation(call_name, args)
16
+ end
17
+
18
+ protected
19
+ def perform_invocation(method_name, args) # :nodoc:
20
+ raise NotImplementedError, "use a protocol-specific client"
21
+ end
22
+
23
+ private
24
+ def method_name(name)
25
+ if @api.has_api_method?(name.to_sym)
26
+ name.to_s
27
+ elsif @api.has_public_api_method?(name.to_s)
28
+ @api.api_method_name(name.to_s).to_s
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+ def lookup_class(klass)
35
+ klass.is_a?(Hash) ? klass.values[0] : klass
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,88 @@
1
+ require 'soap/rpc/driver'
2
+ require 'uri'
3
+
4
+ module ActionWebService # :nodoc:
5
+ module Client # :nodoc:
6
+
7
+ # Implements SOAP client support (using RPC encoding for the messages).
8
+ #
9
+ # ==== Example Usage
10
+ #
11
+ # class PersonAPI < ActionWebService::API::Base
12
+ # api_method :find_all, :returns => [[Person]]
13
+ # end
14
+ #
15
+ # soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...")
16
+ # persons = soap_client.find_all
17
+ #
18
+ class Soap < Base
19
+
20
+ # Creates a new web service client using the SOAP RPC protocol.
21
+ #
22
+ # +api+ must be an ActionWebService::API::Base derivative, and
23
+ # +endpoint_uri+ must point at the relevant URL to which protocol requests
24
+ # will be sent with HTTP POST.
25
+ #
26
+ # Valid options:
27
+ # [<tt>:service_name</tt>] If the remote server has used a custom +wsdl_service_name+
28
+ # option, you must specify it here
29
+ def initialize(api, endpoint_uri, options={})
30
+ super(api, endpoint_uri)
31
+ @service_name = options[:service_name] || 'ActionWebService'
32
+ @namespace = "urn:#{@service_name}"
33
+ @mapper = ActionWebService::Protocol::Soap::SoapMapper.new(@namespace)
34
+ @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new(@mapper)
35
+ @soap_action_base = options[:soap_action_base]
36
+ @soap_action_base ||= URI.parse(endpoint_uri).path
37
+ @driver = create_soap_rpc_driver(api, endpoint_uri)
38
+ end
39
+
40
+ protected
41
+ def perform_invocation(method_name, args)
42
+ @driver.send(method_name, *args)
43
+ end
44
+
45
+ def soap_action(method_name)
46
+ "#{@soap_action_base}/#{method_name}"
47
+ end
48
+
49
+ private
50
+ def create_soap_rpc_driver(api, endpoint_uri)
51
+ @mapper.map_api(api)
52
+ driver = SoapDriver.new(endpoint_uri, nil)
53
+ driver.mapping_registry = @mapper.registry
54
+ api.api_methods.each do |name, info|
55
+ public_name = api.public_api_method_name(name)
56
+ qname = XSD::QName.new(@namespace, public_name)
57
+ action = soap_action(public_name)
58
+ expects = info[:expects]
59
+ returns = info[:returns]
60
+ param_def = []
61
+ i = 1
62
+ if expects
63
+ expects.each do |klass|
64
+ param_name = klass.is_a?(Hash) ? klass.keys[0] : "param#{i}"
65
+ param_klass = lookup_class(klass)
66
+ mapping = @mapper.lookup(param_klass)
67
+ param_def << ['in', param_name, mapping.registry_mapping]
68
+ i += 1
69
+ end
70
+ end
71
+ if returns
72
+ mapping = @mapper.lookup(lookup_class(returns[0]))
73
+ param_def << ['retval', 'return', mapping.registry_mapping]
74
+ end
75
+ driver.add_method(qname, action, name.to_s, param_def)
76
+ end
77
+ driver
78
+ end
79
+
80
+ class SoapDriver < SOAP::RPC::Driver # :nodoc:
81
+ def add_method(qname, soapaction, name, param_def)
82
+ @proxy.add_rpc_method(qname, soapaction, name, param_def)
83
+ add_rpc_method_interface(name, param_def)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,77 @@
1
+ require 'uri'
2
+ require 'xmlrpc/client'
3
+
4
+ module ActionWebService # :nodoc:
5
+ module Client # :nodoc:
6
+
7
+ # Implements XML-RPC client support
8
+ #
9
+ # ==== Example Usage
10
+ #
11
+ # class BloggerAPI < ActionWebService::API::Base
12
+ # inflect_names false
13
+ # api_method :getRecentPosts, :returns => [[Blog::Post]]
14
+ # end
15
+ #
16
+ # blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger")
17
+ # posts = blog.getRecentPosts
18
+ class XmlRpc < Base
19
+
20
+ # Creates a new web service client using the XML-RPC protocol.
21
+ #
22
+ # +api+ must be an ActionWebService::API::Base derivative, and
23
+ # +endpoint_uri+ must point at the relevant URL to which protocol requests
24
+ # will be sent with HTTP POST.
25
+ #
26
+ # Valid options:
27
+ # [<tt>:handler_name</tt>] If the remote server defines its services inside special
28
+ # handler (the Blogger API uses a <tt>"blogger"</tt> handler name for example),
29
+ # provide it here, or your method calls will fail
30
+ def initialize(api, endpoint_uri, options={})
31
+ @api = api
32
+ @handler_name = options[:handler_name]
33
+ @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
34
+ end
35
+
36
+ protected
37
+ def perform_invocation(method_name, args)
38
+ args = transform_outgoing_method_params(method_name, args)
39
+ ok, return_value = @client.call2(public_name(method_name), *args)
40
+ return transform_return_value(method_name, return_value) if ok
41
+ raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
42
+ end
43
+
44
+ def transform_outgoing_method_params(method_name, params)
45
+ info = @api.api_methods[method_name.to_sym]
46
+ signature = info[:expects]
47
+ signature_length = signature.nil?? 0 : signature.length
48
+ if signature_length != params.length
49
+ raise(ProtocolError, "API declares #{public_name(method_name)} to accept " +
50
+ "#{signature_length} parameters, but #{params.length} parameters " +
51
+ "were supplied")
52
+ end
53
+ if signature_length > 0
54
+ signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature)
55
+ (1..signature.size).each do |i|
56
+ i -= 1
57
+ params[i] = Protocol::XmlRpc::XmlRpcProtocol.ruby_to_xmlrpc(params[i], lookup_class(signature[i]))
58
+ end
59
+ end
60
+ params
61
+ end
62
+
63
+ def transform_return_value(method_name, return_value)
64
+ info = @api.api_methods[method_name.to_sym]
65
+ return true unless signature = info[:returns]
66
+ param_klass = lookup_class(signature[0])
67
+ signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types([param_klass])
68
+ Protocol::XmlRpc::XmlRpcProtocol.xmlrpc_to_ruby(return_value, signature[0])
69
+ end
70
+
71
+ def public_name(method_name)
72
+ public_name = @api.public_api_method_name(method_name)
73
+ @handler_name ? "#{@handler_name}.#{public_name}" : public_name
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,85 @@
1
+ module ActionWebService # :nodoc:
2
+ module Container # :nodoc:
3
+ class ContainerError < ActionWebService::ActionWebServiceError # :nodoc:
4
+ end
5
+
6
+ def self.append_features(base) # :nodoc:
7
+ super
8
+ base.extend(ClassMethods)
9
+ base.send(:include, ActionWebService::Container::InstanceMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ # Declares a web service that will provides access to the API of the given
14
+ # +object+. +object+ must be an ActionWebService::Base derivative.
15
+ #
16
+ # Web service object creation can either be _immediate_, where the object
17
+ # instance is given at class definition time, or _deferred_, where
18
+ # object instantiation is delayed until request time.
19
+ #
20
+ # ==== Immediate web service object example
21
+ #
22
+ # class ApiController < ApplicationController
23
+ # web_service_dispatching_mode :delegated
24
+ #
25
+ # web_service :person, PersonService.new
26
+ # end
27
+ #
28
+ # For deferred instantiation, a block should be given instead of an
29
+ # object instance. This block will be executed in controller instance
30
+ # context, so it can rely on controller instance variables being present.
31
+ #
32
+ # ==== Deferred web service object example
33
+ #
34
+ # class ApiController < ApplicationController
35
+ # web_service_dispatching_mode :delegated
36
+ #
37
+ # web_service(:person) { PersonService.new(@request.env) }
38
+ # end
39
+ def web_service(name, object=nil, &block)
40
+ if (object && block_given?) || (object.nil? && block.nil?)
41
+ raise(ContainerError, "either service, or a block must be given")
42
+ end
43
+ name = name.to_sym
44
+ if block_given?
45
+ info = { name => { :block => block } }
46
+ else
47
+ info = { name => { :object => object } }
48
+ end
49
+ write_inheritable_hash("web_services", info)
50
+ call_web_service_definition_callbacks(self, name, info)
51
+ end
52
+
53
+ # Whether this service contains a service with the given +name+
54
+ def has_web_service?(name)
55
+ web_services.has_key?(name.to_sym)
56
+ end
57
+
58
+ def web_services # :nodoc:
59
+ read_inheritable_attribute("web_services") || {}
60
+ end
61
+
62
+ def add_web_service_definition_callback(&block) # :nodoc:
63
+ write_inheritable_array("web_service_definition_callbacks", [block])
64
+ end
65
+
66
+ private
67
+ def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
68
+ (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
69
+ block.call(container_class, web_service_name, service_info)
70
+ end
71
+ end
72
+ end
73
+
74
+ module InstanceMethods # :nodoc:
75
+ def web_service_object(web_service_name)
76
+ info = self.class.web_services[web_service_name.to_sym]
77
+ unless info
78
+ raise(ContainerError, "no such web service '#{web_service_name}'")
79
+ end
80
+ service = info[:block]
81
+ service ? instance_eval(&service) : info[:object]
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,2 @@
1
+ require 'action_web_service/dispatcher/abstract'
2
+ require 'action_web_service/dispatcher/action_controller_dispatcher'
@@ -0,0 +1,150 @@
1
+ require 'benchmark'
2
+
3
+ module ActionWebService # :nodoc:
4
+ module Dispatcher # :nodoc:
5
+ class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
6
+ end
7
+
8
+ def self.append_features(base) # :nodoc:
9
+ super
10
+ base.class_inheritable_option(:web_service_dispatching_mode, :direct)
11
+ base.class_inheritable_option(:web_service_exception_reporting, true)
12
+ base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
13
+ end
14
+
15
+ module InstanceMethods # :nodoc:
16
+ private
17
+ def dispatch_web_service_request(action_pack_request)
18
+ protocol_request = protocol_response = nil
19
+ bm = Benchmark.measure do
20
+ protocol_request = probe_request_protocol(action_pack_request)
21
+ protocol_response = dispatch_protocol_request(protocol_request)
22
+ end
23
+ [protocol_request, protocol_response, bm.real, nil]
24
+ rescue Exception => e
25
+ protocol_response = prepare_exception_response(protocol_request, e)
26
+ [protocol_request, prepare_exception_response(protocol_request, e), nil, e]
27
+ end
28
+
29
+ def dispatch_protocol_request(protocol_request)
30
+ case web_service_dispatching_mode
31
+ when :direct
32
+ dispatch_direct_request(protocol_request)
33
+ when :delegated
34
+ dispatch_delegated_request(protocol_request)
35
+ else
36
+ raise(ContainerError, "unsupported dispatching mode :#{web_service_dispatching_mode}")
37
+ end
38
+ end
39
+
40
+ def dispatch_direct_request(protocol_request)
41
+ request = prepare_dispatch_request(protocol_request)
42
+ return_value = direct_invoke(request)
43
+ protocol_request.marshal(return_value)
44
+ end
45
+
46
+ def dispatch_delegated_request(protocol_request)
47
+ request = prepare_dispatch_request(protocol_request)
48
+ return_value = delegated_invoke(request)
49
+ protocol_request.marshal(return_value)
50
+ end
51
+
52
+ def direct_invoke(request)
53
+ return nil unless before_direct_invoke(request)
54
+ return_value = send(request.method_name)
55
+ after_direct_invoke(request)
56
+ return_value
57
+ end
58
+
59
+ def before_direct_invoke(request)
60
+ @method_params = request.params
61
+ end
62
+
63
+ def after_direct_invoke(request)
64
+ end
65
+
66
+ def delegated_invoke(request)
67
+ cancellation_reason = nil
68
+ web_service = request.web_service
69
+ return_value = web_service.perform_invocation(request.method_name, request.params) do |x|
70
+ cancellation_reason = x
71
+ end
72
+ if cancellation_reason
73
+ raise(DispatcherError, "request canceled: #{cancellation_reason}")
74
+ end
75
+ return_value
76
+ end
77
+
78
+ def prepare_dispatch_request(protocol_request)
79
+ api = method_name = web_service_name = web_service = params = nil
80
+ public_method_name = protocol_request.public_method_name
81
+ case web_service_dispatching_mode
82
+ when :direct
83
+ api = self.class.web_service_api
84
+ when :delegated
85
+ web_service_name = protocol_request.web_service_name
86
+ web_service = web_service_object(web_service_name)
87
+ api = web_service.class.web_service_api
88
+ end
89
+ method_name = api.api_method_name(public_method_name)
90
+ signature = nil
91
+ if method_name
92
+ signature = api.api_methods[method_name]
93
+ protocol_request.type = Protocol::CheckedMessage
94
+ protocol_request.signature = signature[:expects]
95
+ protocol_request.return_signature = signature[:returns]
96
+ else
97
+ method_name = api.default_api_method
98
+ if method_name
99
+ protocol_request.type = Protocol::UncheckedMessage
100
+ else
101
+ raise(DispatcherError, "no such method #{web_service_name}##{public_method_name}")
102
+ end
103
+ end
104
+ params = protocol_request.unmarshal
105
+ DispatchRequest.new(
106
+ :api => api,
107
+ :public_method_name => public_method_name,
108
+ :method_name => method_name,
109
+ :signature => signature,
110
+ :web_service_name => web_service_name,
111
+ :web_service => web_service,
112
+ :params => params)
113
+ end
114
+
115
+ def prepare_exception_response(protocol_request, exception)
116
+ if protocol_request && exception
117
+ case web_service_dispatching_mode
118
+ when :direct
119
+ if web_service_exception_reporting
120
+ return protocol_request.protocol.marshal_exception(exception)
121
+ end
122
+ when :delegated
123
+ web_service = web_service_object(protocol_request.web_service_name)
124
+ if web_service && web_service.class.web_service_exception_reporting
125
+ return protocol_request.protocol.marshal_exception(exception)
126
+ end
127
+ end
128
+ else
129
+ protocol_request.protocol.marshal_exception(RuntimeError.new("missing protocol request or exception"))
130
+ end
131
+ rescue Exception
132
+ nil
133
+ end
134
+
135
+ class DispatchRequest
136
+ attr :api
137
+ attr :public_method_name
138
+ attr :method_name
139
+ attr :signature
140
+ attr :web_service_name
141
+ attr :web_service
142
+ attr :params
143
+
144
+ def initialize(values={})
145
+ values.each{|k,v| instance_variable_set("@#{k.to_s}", v)}
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end