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,299 @@
1
+ module ActionWebService # :nodoc:
2
+ module Dispatcher # :nodoc:
3
+ module ActionController # :nodoc:
4
+ def self.append_features(base) # :nodoc:
5
+ super
6
+ base.class_eval do
7
+ class << self
8
+ alias_method :inherited_without_action_controller, :inherited
9
+ end
10
+ alias_method :before_direct_invoke_without_action_controller, :before_direct_invoke
11
+ alias_method :after_direct_invoke_without_action_controller, :after_direct_invoke
12
+ end
13
+ base.add_web_service_api_callback do |klass, api|
14
+ if klass.web_service_dispatching_mode == :direct
15
+ klass.class_eval <<-EOS
16
+ def api
17
+ controller_dispatch_web_service_request
18
+ end
19
+ EOS
20
+ end
21
+ end
22
+ base.add_web_service_definition_callback do |klass, name, info|
23
+ if klass.web_service_dispatching_mode == :delegated
24
+ klass.class_eval <<-EOS
25
+ def #{name}
26
+ controller_dispatch_web_service_request
27
+ end
28
+ EOS
29
+ end
30
+ end
31
+ base.extend(ClassMethods)
32
+ base.send(:include, ActionWebService::Dispatcher::ActionController::Invocation)
33
+ end
34
+
35
+ module ClassMethods # :nodoc:
36
+ def inherited(child)
37
+ inherited_without_action_controller(child)
38
+ child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlGeneration)
39
+ end
40
+ end
41
+
42
+ module Invocation # :nodoc:
43
+ private
44
+ def controller_dispatch_web_service_request
45
+ request, response, elapsed, exception = dispatch_web_service_request(@request)
46
+ if response
47
+ begin
48
+ log_request(request)
49
+ log_error(exception) if exception && logger
50
+ log_response(response, elapsed)
51
+ response_options = { :type => response.content_type, :disposition => 'inline' }
52
+ send_data(response.raw_body, response_options)
53
+ rescue Exception => e
54
+ log_error(e) unless logger.nil?
55
+ render_text("Internal protocol error", "500 Internal Server Error")
56
+ end
57
+ else
58
+ logger.error("No response available") unless logger.nil?
59
+ render_text("Internal protocol error", "500 Internal Server Error")
60
+ end
61
+ end
62
+
63
+ def before_direct_invoke(request)
64
+ before_direct_invoke_without_action_controller(request)
65
+ @params ||= {}
66
+ signature = request.signature
67
+ if signature && (expects = request.signature[:expects])
68
+ (0..(@method_params.size-1)).each do |i|
69
+ if expects[i].is_a?(Hash)
70
+ @params[expects[i].keys[0].to_s] = @method_params[i]
71
+ else
72
+ @params['param%d' % i] = @method_params[i]
73
+ end
74
+ end
75
+ end
76
+ @params['action'] = request.method_name.to_s
77
+ @session ||= {}
78
+ @assigns ||= {}
79
+ return nil if before_action == false
80
+ true
81
+ end
82
+
83
+ def after_direct_invoke(request)
84
+ after_direct_invoke_without_action_controller(request)
85
+ after_action
86
+ end
87
+
88
+ def log_request(request)
89
+ unless logger.nil? || request.nil?
90
+ logger.debug("\nWeb Service Request:")
91
+ indented = request.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
92
+ logger.debug(indented)
93
+ end
94
+ end
95
+
96
+ def log_response(response, elapsed)
97
+ unless logger.nil? || response.nil?
98
+ logger.debug("\nWeb Service Response (%f):" % elapsed)
99
+ indented = response.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
100
+ logger.debug(indented)
101
+ end
102
+ end
103
+
104
+ unless method_defined?(:logger)
105
+ def logger; @logger; end
106
+ end
107
+ end
108
+
109
+ module WsdlGeneration # :nodoc:
110
+ XsdNs = 'http://www.w3.org/2001/XMLSchema'
111
+ WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
112
+ SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
113
+ SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
114
+ SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
115
+
116
+ def wsdl
117
+ case @request.method
118
+ when :get
119
+ begin
120
+ host_name = @request.env['HTTP_HOST'] || @request.env['SERVER_NAME']
121
+ uri = "http://#{host_name}/#{controller_name}/"
122
+ soap_action_base = "/#{controller_name}"
123
+ xml = to_wsdl(self, uri, soap_action_base)
124
+ send_data(xml, :type => 'text/xml', :disposition => 'inline')
125
+ rescue Exception => e
126
+ log_error e unless logger.nil?
127
+ render_text('', "500 #{e.message}")
128
+ end
129
+ when :post
130
+ render_text('', "500 POST not supported")
131
+ end
132
+ end
133
+
134
+ private
135
+ def to_wsdl(container, uri, soap_action_base)
136
+ wsdl = ""
137
+
138
+ web_service_dispatching_mode = container.web_service_dispatching_mode
139
+ mapper = container.class.soap_mapper
140
+ namespace = mapper.custom_namespace
141
+ wsdl_service_name = namespace.split(/:/)[1]
142
+
143
+ services = {}
144
+ mapper.map_container_services(container) do |name, api, api_methods|
145
+ services[name] = [api, api_methods]
146
+ end
147
+ custom_types = mapper.custom_types
148
+
149
+
150
+ xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2)
151
+ xm.instruct!
152
+
153
+ xm.definitions('name' => wsdl_service_name,
154
+ 'targetNamespace' => namespace,
155
+ 'xmlns:typens' => namespace,
156
+ 'xmlns:xsd' => XsdNs,
157
+ 'xmlns:soap' => SoapNs,
158
+ 'xmlns:soapenc' => SoapEncodingNs,
159
+ 'xmlns:wsdl' => WsdlNs,
160
+ 'xmlns' => WsdlNs) do
161
+
162
+ # Custom type XSD generation
163
+ if custom_types.size > 0
164
+ xm.types do
165
+ xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
166
+ custom_types.each do |klass, mapping|
167
+ case
168
+ when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping)
169
+ xm.xsd(:complexType, 'name' => mapping.type_name) do
170
+ xm.xsd(:complexContent) do
171
+ xm.xsd(:restriction, 'base' => 'soapenc:Array') do
172
+ xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
173
+ 'wsdl:arrayType' => mapping.element_mapping.qualified_type_name + '[]')
174
+ end
175
+ end
176
+ end
177
+ when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping)
178
+ xm.xsd(:complexType, 'name' => mapping.type_name) do
179
+ xm.xsd(:all) do
180
+ mapping.each_attribute do |name, type_name|
181
+ xm.xsd(:element, 'name' => name, 'type' => type_name)
182
+ end
183
+ end
184
+ end
185
+ else
186
+ raise(WsdlError, "unsupported mapping type #{mapping.class.name}")
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ services.each do |service_name, service_values|
194
+ service_api, api_methods = service_values
195
+ # Parameter list message definitions
196
+ api_methods.each do |method_name, method_signature|
197
+ gen = lambda do |msg_name, direction|
198
+ xm.message('name' => msg_name) do
199
+ sym = nil
200
+ if direction == :out
201
+ if method_signature[:returns]
202
+ xm.part('name' => 'return', 'type' => method_signature[:returns][0].qualified_type_name)
203
+ end
204
+ else
205
+ mapping_list = method_signature[:expects]
206
+ i = 1
207
+ mapping_list.each do |mapping|
208
+ if mapping.is_a?(Hash)
209
+ param_name = mapping.keys.shift
210
+ mapping = mapping.values.shift
211
+ else
212
+ param_name = "param#{i}"
213
+ end
214
+ xm.part('name' => param_name, 'type' => mapping.qualified_type_name)
215
+ i += 1
216
+ end if mapping_list
217
+ end
218
+ end
219
+ end
220
+ public_name = service_api.public_api_method_name(method_name)
221
+ gen.call(public_name, :in)
222
+ gen.call("#{public_name}Response", :out)
223
+ end
224
+
225
+ # Declare the port
226
+ port_name = port_name_for(wsdl_service_name, service_name)
227
+ xm.portType('name' => port_name) do
228
+ api_methods.each do |method_name, method_signature|
229
+ public_name = service_api.public_api_method_name(method_name)
230
+ xm.operation('name' => public_name) do
231
+ xm.input('message' => "typens:#{public_name}")
232
+ xm.output('message' => "typens:#{public_name}Response")
233
+ end
234
+ end
235
+ end
236
+
237
+ # Bind the port to SOAP
238
+ binding_name = binding_name_for(wsdl_service_name, service_name)
239
+ xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
240
+ xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
241
+ api_methods.each do |method_name, method_signature|
242
+ public_name = service_api.public_api_method_name(method_name)
243
+ xm.operation('name' => public_name) do
244
+ case web_service_dispatching_mode
245
+ when :direct
246
+ soap_action = soap_action_base + "/api/" + public_name
247
+ when :delegated
248
+ soap_action = soap_action_base \
249
+ + "/" + service_name.to_s \
250
+ + "/" + public_name
251
+ end
252
+ xm.soap(:operation, 'soapAction' => soap_action)
253
+ xm.input do
254
+ xm.soap(:body,
255
+ 'use' => 'encoded',
256
+ 'namespace' => namespace,
257
+ 'encodingStyle' => SoapEncodingNs)
258
+ end
259
+ xm.output do
260
+ xm.soap(:body,
261
+ 'use' => 'encoded',
262
+ 'namespace' => namespace,
263
+ 'encodingStyle' => SoapEncodingNs)
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ # Define the service
271
+ xm.service('name' => "#{wsdl_service_name}Service") do
272
+ services.each do |service_name, service_values|
273
+ port_name = port_name_for(wsdl_service_name, service_name)
274
+ binding_name = binding_name_for(wsdl_service_name, service_name)
275
+ case web_service_dispatching_mode
276
+ when :direct
277
+ binding_target = 'api'
278
+ when :delegated
279
+ binding_target = service_name.to_s
280
+ end
281
+ xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
282
+ xm.soap(:address, 'location' => "#{uri}#{binding_target}")
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ def port_name_for(wsdl_service_name, service_name)
290
+ "#{wsdl_service_name}#{service_name.to_s.camelize}Port"
291
+ end
292
+
293
+ def binding_name_for(wsdl_service_name, service_name)
294
+ "#{wsdl_service_name}#{service_name.to_s.camelize}Binding"
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,205 @@
1
+ module ActionWebService # :nodoc:
2
+ module Invocation # :nodoc:
3
+ class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
4
+ end
5
+
6
+ def self.append_features(base) # :nodoc:
7
+ super
8
+ base.extend(ClassMethods)
9
+ base.send(:include, ActionWebService::Invocation::InstanceMethods)
10
+ end
11
+
12
+ # Invocation interceptors provide a means to execute custom code before
13
+ # and after method invocations on ActionWebService::Base objects.
14
+ #
15
+ # When running in _Direct_ dispatching mode, ActionController filters
16
+ # should be used for this functionality instead.
17
+ #
18
+ # The semantics of invocation interceptors are the same as ActionController
19
+ # filters, and accept the same parameters and options.
20
+ #
21
+ # A _before_ interceptor can also cancel execution by returning +false+,
22
+ # or returning a <tt>[false, "cancel reason"]</tt> array if it wishes to supply
23
+ # a reason for canceling the request.
24
+ #
25
+ # === Example
26
+ #
27
+ # class CustomService < ActionWebService::Base
28
+ # before_invocation :intercept_add, :only => [:add]
29
+ #
30
+ # def add(a, b)
31
+ # a + b
32
+ # end
33
+ #
34
+ # private
35
+ # def intercept_add
36
+ # return [false, "permission denied"] # cancel it
37
+ # end
38
+ # end
39
+ #
40
+ # Options:
41
+ # [<tt>:except</tt>] A list of methods for which the interceptor will NOT be called
42
+ # [<tt>:only</tt>] A list of methods for which the interceptor WILL be called
43
+ module ClassMethods
44
+ # Appends the given +interceptors+ to be called
45
+ # _before_ method invocation.
46
+ def append_before_invocation(*interceptors, &block)
47
+ conditions = extract_conditions!(interceptors)
48
+ interceptors << block if block_given?
49
+ add_interception_conditions(interceptors, conditions)
50
+ append_interceptors_to_chain("before", interceptors)
51
+ end
52
+
53
+ # Prepends the given +interceptors+ to be called
54
+ # _before_ method invocation.
55
+ def prepend_before_invocation(*interceptors, &block)
56
+ conditions = extract_conditions!(interceptors)
57
+ interceptors << block if block_given?
58
+ add_interception_conditions(interceptors, conditions)
59
+ prepend_interceptors_to_chain("before", interceptors)
60
+ end
61
+
62
+ alias :before_invocation :append_before_invocation
63
+
64
+ # Appends the given +interceptors+ to be called
65
+ # _after_ method invocation.
66
+ def append_after_invocation(*interceptors, &block)
67
+ conditions = extract_conditions!(interceptors)
68
+ interceptors << block if block_given?
69
+ add_interception_conditions(interceptors, conditions)
70
+ append_interceptors_to_chain("after", interceptors)
71
+ end
72
+
73
+ # Prepends the given +interceptors+ to be called
74
+ # _after_ method invocation.
75
+ def prepend_after_invocation(*interceptors, &block)
76
+ conditions = extract_conditions!(interceptors)
77
+ interceptors << block if block_given?
78
+ add_interception_conditions(interceptors, conditions)
79
+ prepend_interceptors_to_chain("after", interceptors)
80
+ end
81
+
82
+ alias :after_invocation :append_after_invocation
83
+
84
+ def before_invocation_interceptors # :nodoc:
85
+ read_inheritable_attribute("before_invocation_interceptors")
86
+ end
87
+
88
+ def after_invocation_interceptors # :nodoc:
89
+ read_inheritable_attribute("after_invocation_interceptors")
90
+ end
91
+
92
+ def included_intercepted_methods # :nodoc:
93
+ read_inheritable_attribute("included_intercepted_methods") || {}
94
+ end
95
+
96
+ def excluded_intercepted_methods # :nodoc:
97
+ read_inheritable_attribute("excluded_intercepted_methods") || {}
98
+ end
99
+
100
+ private
101
+ def append_interceptors_to_chain(condition, interceptors)
102
+ write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
103
+ end
104
+
105
+ def prepend_interceptors_to_chain(condition, interceptors)
106
+ interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors")
107
+ write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors)
108
+ end
109
+
110
+ def extract_conditions!(interceptors)
111
+ return nil unless interceptors.last.is_a? Hash
112
+ interceptors.pop
113
+ end
114
+
115
+ def add_interception_conditions(interceptors, conditions)
116
+ return unless conditions
117
+ included, excluded = conditions[:only], conditions[:except]
118
+ write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included
119
+ write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded
120
+ end
121
+
122
+ def condition_hash(interceptors, *methods)
123
+ interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
124
+ end
125
+ end
126
+
127
+ module InstanceMethods # :nodoc:
128
+ def self.append_features(base)
129
+ super
130
+ base.class_eval do
131
+ alias_method :perform_invocation_without_interception, :perform_invocation
132
+ alias_method :perform_invocation, :perform_invocation_with_interception
133
+ end
134
+ end
135
+
136
+ def perform_invocation_with_interception(method_name, params, &block)
137
+ return if before_invocation(method_name, params, &block) == false
138
+ return_value = perform_invocation_without_interception(method_name, params)
139
+ after_invocation(method_name, params, return_value)
140
+ return_value
141
+ end
142
+
143
+ def perform_invocation(method_name, params)
144
+ send(method_name, *params)
145
+ end
146
+
147
+ def before_invocation(name, args, &block)
148
+ call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
149
+ end
150
+
151
+ def after_invocation(name, args, result)
152
+ call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
153
+ end
154
+
155
+ private
156
+
157
+ def call_interceptors(interceptors, interceptor_args, &block)
158
+ if interceptors and not interceptors.empty?
159
+ interceptors.each do |interceptor|
160
+ next if method_exempted?(interceptor, interceptor_args[0].to_s)
161
+ result = case
162
+ when interceptor.is_a?(Symbol)
163
+ self.send(interceptor, *interceptor_args)
164
+ when interceptor_block?(interceptor)
165
+ interceptor.call(self, *interceptor_args)
166
+ when interceptor_class?(interceptor)
167
+ interceptor.intercept(self, *interceptor_args)
168
+ else
169
+ raise(
170
+ InvocationError,
171
+ "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
172
+ )
173
+ end
174
+ reason = nil
175
+ if result.is_a?(Array)
176
+ reason = result[1] if result[1]
177
+ result = result[0]
178
+ end
179
+ if result == false
180
+ block.call(reason) if block && reason
181
+ return false
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ def interceptor_block?(interceptor)
188
+ interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
189
+ end
190
+
191
+ def interceptor_class?(interceptor)
192
+ interceptor.respond_to?("intercept")
193
+ end
194
+
195
+ def method_exempted?(interceptor, method_name)
196
+ case
197
+ when self.class.included_intercepted_methods[interceptor]
198
+ !self.class.included_intercepted_methods[interceptor].include?(method_name)
199
+ when self.class.excluded_intercepted_methods[interceptor]
200
+ self.class.excluded_intercepted_methods[interceptor].include?(method_name)
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end