actionwebservice 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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