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,420 @@
1
+ # encoding: UTF-8
2
+ require 'benchmark'
3
+ require 'builder/xmlmarkup'
4
+
5
+ module ActionWebService # :nodoc:
6
+ module Dispatcher # :nodoc:
7
+ module ActionController # :nodoc:
8
+ def self.included(base) # :nodoc:
9
+ class << base
10
+ include ClassMethods
11
+ alias_method_chain :inherited, :action_controller
12
+ end
13
+ base.class_eval do
14
+ alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
15
+ end
16
+ base.add_web_service_api_callback do |klass, api|
17
+ if klass.web_service_dispatching_mode == :direct
18
+ klass.class_eval 'def api; dispatch_web_service_request; end'
19
+ end
20
+ end
21
+ base.add_web_service_definition_callback do |klass, name, info|
22
+ if klass.web_service_dispatching_mode == :delegated
23
+ klass.class_eval "def #{name}; dispatch_web_service_request; end"
24
+ elsif klass.web_service_dispatching_mode == :layered
25
+ klass.class_eval 'def api; dispatch_web_service_request; end'
26
+ end
27
+ end
28
+ base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
29
+ end
30
+
31
+ module ClassMethods # :nodoc:
32
+ def inherited_with_action_controller(child)
33
+ inherited_without_action_controller(child)
34
+ # child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
35
+ end
36
+ end
37
+
38
+ module InstanceMethods # :nodoc:
39
+ private
40
+ def dispatch_web_service_request
41
+ method = request.method.to_s.upcase
42
+ allowed_methods = self.class.web_service_api ? (self.class.web_service_api.allowed_http_methods || []) : [ :post ]
43
+ allowed_methods = allowed_methods.map{|m| m.to_s.upcase }
44
+ if !allowed_methods.include?(method)
45
+ render :text => "#{method} not supported", :status=>500
46
+ return
47
+ end
48
+ exception = nil
49
+ begin
50
+ ws_request = discover_web_service_request(request)
51
+ rescue Exception => e
52
+ exception = e
53
+ end
54
+ if ws_request
55
+ ws_response = nil
56
+ exception = nil
57
+ bm = Benchmark.measure do
58
+ begin
59
+ ws_response = invoke_web_service_request(ws_request)
60
+ rescue Exception => e
61
+ exception = e
62
+ end
63
+ end
64
+ log_request(ws_request, request.raw_post)
65
+ if exception
66
+ log_error(exception) unless logger.nil?
67
+ send_web_service_error_response(ws_request, exception)
68
+ else
69
+ send_web_service_response(ws_response, bm.real)
70
+ end
71
+ else
72
+ exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
73
+ log_error(exception) unless logger.nil?
74
+ send_web_service_error_response(ws_request, exception)
75
+ end
76
+ rescue Exception => e
77
+ log_error(e) unless logger.nil?
78
+ send_web_service_error_response(ws_request, e)
79
+ end
80
+
81
+ def send_web_service_response(ws_response, elapsed=nil)
82
+ log_response(ws_response, elapsed)
83
+ options = { :type => ws_response.content_type, :disposition => 'inline' }
84
+ send_data(ws_response.body, options)
85
+ end
86
+
87
+ def send_web_service_error_response(ws_request, exception)
88
+ if ws_request
89
+ unless self.class.web_service_exception_reporting
90
+ exception = DispatcherError.new("Internal server error (exception raised)")
91
+ end
92
+ api_method = ws_request.api_method
93
+ public_method_name = api_method ? api_method.public_name : ws_request.method_name
94
+ return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
95
+ ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
96
+ send_web_service_response(ws_response)
97
+ else
98
+ if self.class.web_service_exception_reporting
99
+ message = exception.message
100
+ backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
101
+ else
102
+ message = "Exception raised"
103
+ backtrace = ""
104
+ end
105
+ render :status => 500, :text => "Internal protocol error: #{message}#{backtrace}"
106
+ end
107
+ end
108
+
109
+ def web_service_direct_invoke(invocation)
110
+ invocation.method_named_params.each do |name, value|
111
+ params[name] = value
112
+ end
113
+ web_service_direct_invoke_without_controller(invocation)
114
+ end
115
+
116
+ def log_request(ws_request, body)
117
+ unless logger.nil?
118
+ name = ws_request.method_name
119
+ api_method = ws_request.api_method
120
+ params = ws_request.method_params
121
+ if api_method && api_method.expects
122
+ params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
123
+ else
124
+ params = params.map{ |param| param.inspect }
125
+ end
126
+ service = ws_request.service_name
127
+ logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
128
+ logger.debug(indent(body))
129
+ end
130
+ end
131
+
132
+ def log_response(ws_response, elapsed=nil)
133
+ unless logger.nil?
134
+ elapsed = (elapsed ? " (%f):" % elapsed : ":")
135
+ logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
136
+ logger.debug(indent(ws_response.body))
137
+ end
138
+ end
139
+
140
+ def log_error(exception)
141
+ return unless logger
142
+
143
+ ActiveSupport::Deprecation.silence do
144
+ if ActionView::TemplateError === exception
145
+ logger.fatal(exception.to_s)
146
+ else
147
+ logger.fatal(
148
+ "\n#{exception.class} (#{exception.message}):\n " +
149
+ clean_backtrace(exception).join("\n ") + "\n\n"
150
+ )
151
+ end
152
+ end
153
+ end
154
+
155
+ def clean_backtrace(exception)
156
+ defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
157
+ Rails.backtrace_cleaner.clean(exception.backtrace) :
158
+ exception.backtrace
159
+ end
160
+
161
+ def indent(body)
162
+ body.split(/\n/).map{|x| " #{x}"}.join("\n")
163
+ end
164
+ end
165
+
166
+ module WsdlAction # :nodoc:
167
+ XsdNs = 'http://www.w3.org/2001/XMLSchema'
168
+ WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
169
+ SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
170
+ SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
171
+ SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
172
+
173
+ def wsdl
174
+ case request.method
175
+ when :get
176
+ begin
177
+ options = { :type => 'text/xml', :disposition => 'inline' }
178
+ send_data(to_wsdl, options)
179
+ rescue Exception => e
180
+ log_error(e) unless logger.nil?
181
+ end
182
+ when :post
183
+ render :status => 500, :text => 'POST not supported'
184
+ end
185
+ end
186
+
187
+ private
188
+ def base_uri
189
+ host = request.host_with_port
190
+ # TODO replace relative_url_root
191
+ relative_url_root = '' #::ActionController::Base.relative_url_root
192
+ scheme = request.ssl? ? 'https' : 'http'
193
+ '%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
194
+ end
195
+
196
+ def to_wsdl
197
+ xml = ''
198
+ inflect = web_service_inflect_type
199
+ dispatching_mode = web_service_dispatching_mode
200
+ global_service_name = wsdl_service_name
201
+ namespace = wsdl_namespace || 'urn:ActionWebService'
202
+ soap_action_base = "/#{controller_name}"
203
+
204
+ marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
205
+ apis = {}
206
+ case dispatching_mode
207
+ when :direct
208
+ api = self.class.web_service_api
209
+ web_service_name = self.class.to_s.sub(/Controller$/, '').underscore
210
+ api = (web_service_name + '_api').classify.constantize unless api
211
+ apis[web_service_name] = [api, register_api(api, marshaler)]
212
+ when :delegated, :layered
213
+ self.class.web_services.each do |web_service_name, info|
214
+ service = web_service_object(web_service_name)
215
+ api = service.class.web_service_api
216
+ apis[web_service_name] = [api, register_api(api, marshaler)]
217
+ end
218
+ end
219
+ custom_types = []
220
+ apis.values.each do |api, bindings|
221
+ bindings.each do |b|
222
+ custom_types << b unless custom_types.include?(b)
223
+ end
224
+ end
225
+
226
+ xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
227
+ xm.instruct!
228
+ xm.definitions('name' => wsdl_service_name,
229
+ 'targetNamespace' => namespace,
230
+ 'xmlns:typens' => namespace,
231
+ 'xmlns:xsd' => XsdNs,
232
+ 'xmlns:soap' => SoapNs,
233
+ 'xmlns:soapenc' => SoapEncodingNs,
234
+ 'xmlns:wsdl' => WsdlNs,
235
+ 'xmlns' => WsdlNs) do
236
+ # Generate XSD
237
+ if custom_types.size > 0
238
+ xm.types do
239
+ xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
240
+ simple_types, array_types, complex_types = [], [], []
241
+ custom_types.each do |binding|
242
+ case
243
+ when binding.type.simple? then simple_types.push binding
244
+ when binding.type.array? then array_types.push binding
245
+ when binding.type.structured? then complex_types.push binding
246
+ end
247
+ end
248
+ simple_types.each do |binding|
249
+ xm.xsd(:simpleType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
250
+ xm.xsd(:restriction, 'base' => "xsd:#{binding.type.base}") do
251
+ binding.type.restrictions do |name, value|
252
+ xm.xsd(name.to_sym, 'value' => value)
253
+ end
254
+ end
255
+ end
256
+ end
257
+ array_types.each do |binding|
258
+ xm.xsd(:complexType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
259
+ xm.xsd(:complexContent) do
260
+ xm.xsd(:restriction, 'base' => 'soapenc:Array') do
261
+ xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
262
+ 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
263
+ end
264
+ end
265
+ end
266
+ end
267
+ complex_types.each do |binding|
268
+ xm.xsd(:complexType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
269
+ xm.xsd(:all) do
270
+ binding.type.each_member do |name, type, options|
271
+ options ||= {}
272
+ b = marshaler.register_type(type)
273
+ xm.xsd(:element, {'name' => inflect ? name.to_s.camelize : name, 'type' => b.qualified_type_name('typens')}.merge(options))
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+ # APIs
283
+ apis.each do |api_name, values|
284
+ api = values[0]
285
+ api.api_methods.each do |name, method|
286
+ gen = lambda do |msg_name, direction|
287
+ xm.message('name' => message_name_for(api_name, msg_name)) do
288
+ sym = nil
289
+ if direction == :out
290
+ returns = method.returns
291
+ if returns
292
+ binding = marshaler.register_type(returns[0])
293
+ xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
294
+ end
295
+ else
296
+ expects = method.expects
297
+ expects.each do |type|
298
+ binding = marshaler.register_type(type)
299
+ xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
300
+ end if expects
301
+ end
302
+ end
303
+ end
304
+ public_name = method.public_name
305
+ gen.call(public_name, :in)
306
+ gen.call("#{public_name}Response", :out)
307
+ end
308
+
309
+ # Port
310
+ port_name = port_name_for(global_service_name, api_name)
311
+ xm.portType('name' => port_name) do
312
+ api.api_methods.each do |name, method|
313
+ xm.operation('name' => method.public_name) do
314
+ xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
315
+ xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
316
+ end
317
+ end
318
+ end
319
+
320
+ # Bind it
321
+ binding_name = binding_name_for(global_service_name, api_name)
322
+ xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
323
+ xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
324
+ api.api_methods.each do |name, method|
325
+ xm.operation('name' => method.public_name) do
326
+ case web_service_dispatching_mode
327
+ when :direct
328
+ soap_action = soap_action_base + "/api/" + method.public_name
329
+ when :delegated, :layered
330
+ soap_action = soap_action_base \
331
+ + "/" + api_name.to_s \
332
+ + "/" + method.public_name
333
+ end
334
+ xm.soap(:operation, 'soapAction' => soap_action)
335
+ xm.input do
336
+ xm.soap(:body,
337
+ 'use' => 'encoded',
338
+ 'namespace' => namespace,
339
+ 'encodingStyle' => SoapEncodingNs)
340
+ end
341
+ xm.output do
342
+ xm.soap(:body,
343
+ 'use' => 'encoded',
344
+ 'namespace' => namespace,
345
+ 'encodingStyle' => SoapEncodingNs)
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ # Define it
353
+ xm.service('name' => "#{global_service_name}Service") do
354
+ apis.each do |api_name, values|
355
+ port_name = port_name_for(global_service_name, api_name)
356
+ binding_name = binding_name_for(global_service_name, api_name)
357
+ case web_service_dispatching_mode
358
+ when :direct, :layered
359
+ binding_target = 'api'
360
+ when :delegated
361
+ binding_target = api_name.to_s
362
+ end
363
+ xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
364
+ xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
365
+ end
366
+ end
367
+ end
368
+ end
369
+ end
370
+
371
+ def port_name_for(global_service, service)
372
+ "#{global_service}#{service.to_s.camelize}Port"
373
+ end
374
+
375
+ def binding_name_for(global_service, service)
376
+ "#{global_service}#{service.to_s.camelize}Binding"
377
+ end
378
+
379
+ def message_name_for(api_name, message_name)
380
+ mode = web_service_dispatching_mode
381
+ if mode == :layered || mode == :delegated
382
+ api_name.to_s + '-' + message_name
383
+ else
384
+ message_name
385
+ end
386
+ end
387
+
388
+ def register_api(api, marshaler)
389
+ bindings = {}
390
+ traverse_custom_types(api, marshaler, bindings) do |binding|
391
+ bindings[binding] = nil unless bindings.has_key?(binding)
392
+ element_binding = binding.element_binding
393
+ bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
394
+ end
395
+ bindings.keys
396
+ end
397
+
398
+ def traverse_custom_types(api, marshaler, bindings, &block)
399
+ api.api_methods.each do |name, method|
400
+ expects, returns = method.expects, method.returns
401
+ expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects
402
+ returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns
403
+ end
404
+ end
405
+
406
+ def traverse_type(marshaler, type, bindings, &block)
407
+ binding = marshaler.register_type(type)
408
+ return if bindings.has_key?(binding)
409
+ bindings[binding] = nil
410
+ yield binding
411
+ if type.array?
412
+ yield marshaler.register_type(type.element_type)
413
+ type = type.element_type
414
+ end
415
+ type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured?
416
+ end
417
+ end
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,3 @@
1
+ # encoding: UTF-8
2
+ require 'action_web_service/dispatcher/abstract'
3
+ require 'action_web_service/dispatcher/action_controller_dispatcher'
@@ -0,0 +1,203 @@
1
+ # encoding: UTF-8
2
+ module ActionWebService # :nodoc:
3
+ module Invocation # :nodoc:
4
+ class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
5
+ end
6
+
7
+ def self.included(base) # :nodoc:
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.included(base)
129
+ base.class_eval do
130
+ alias_method_chain :perform_invocation, :interception
131
+ end
132
+ end
133
+
134
+ def perform_invocation_with_interception(method_name, params, &block)
135
+ return if before_invocation(method_name, params, &block) == false
136
+ return_value = perform_invocation_without_interception(method_name, params)
137
+ after_invocation(method_name, params, return_value)
138
+ return_value
139
+ end
140
+
141
+ def perform_invocation(method_name, params)
142
+ send(method_name, *params)
143
+ end
144
+
145
+ def before_invocation(name, args, &block)
146
+ call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
147
+ end
148
+
149
+ def after_invocation(name, args, result)
150
+ call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
151
+ end
152
+
153
+ private
154
+
155
+ def call_interceptors(interceptors, interceptor_args, &block)
156
+ if interceptors and not interceptors.empty?
157
+ interceptors.each do |interceptor|
158
+ next if method_exempted?(interceptor, interceptor_args[0].to_s)
159
+ result = case
160
+ when interceptor.is_a?(Symbol)
161
+ self.send(interceptor, *interceptor_args)
162
+ when interceptor_block?(interceptor)
163
+ interceptor.call(self, *interceptor_args)
164
+ when interceptor_class?(interceptor)
165
+ interceptor.intercept(self, *interceptor_args)
166
+ else
167
+ raise(
168
+ InvocationError,
169
+ "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
170
+ )
171
+ end
172
+ reason = nil
173
+ if result.is_a?(Array)
174
+ reason = result[1] if result[1]
175
+ result = result[0]
176
+ end
177
+ if result == false
178
+ block.call(reason) if block && reason
179
+ return false
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ def interceptor_block?(interceptor)
186
+ interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
187
+ end
188
+
189
+ def interceptor_class?(interceptor)
190
+ interceptor.respond_to?("intercept")
191
+ end
192
+
193
+ def method_exempted?(interceptor, method_name)
194
+ case
195
+ when self.class.included_intercepted_methods[interceptor]
196
+ !self.class.included_intercepted_methods[interceptor].include?(method_name)
197
+ when self.class.excluded_intercepted_methods[interceptor]
198
+ self.class.excluded_intercepted_methods[interceptor].include?(method_name)
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end