rubyjedi-actionwebservice 2.3.5.20100615120735

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 (85) hide show
  1. data/CHANGELOG +335 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +381 -0
  4. data/Rakefile +180 -0
  5. data/TODO +32 -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 +17 -0
  14. data/examples/metaWeblog/apis/blogger_api.rb +60 -0
  15. data/examples/metaWeblog/apis/blogger_service.rb +34 -0
  16. data/examples/metaWeblog/apis/meta_weblog_api.rb +67 -0
  17. data/examples/metaWeblog/apis/meta_weblog_service.rb +48 -0
  18. data/examples/metaWeblog/controllers/xmlrpc_controller.rb +16 -0
  19. data/generators/web_service/USAGE +28 -0
  20. data/generators/web_service/templates/api_definition.rb +5 -0
  21. data/generators/web_service/templates/controller.rb +8 -0
  22. data/generators/web_service/templates/functional_test.rb +19 -0
  23. data/generators/web_service/web_service_generator.rb +29 -0
  24. data/lib/action_web_service/acts_as_web_service.rb +24 -0
  25. data/lib/action_web_service/api.rb +297 -0
  26. data/lib/action_web_service/base.rb +38 -0
  27. data/lib/action_web_service/casting.rb +151 -0
  28. data/lib/action_web_service/client/base.rb +28 -0
  29. data/lib/action_web_service/client/soap_client.rb +113 -0
  30. data/lib/action_web_service/client/xmlrpc_client.rb +58 -0
  31. data/lib/action_web_service/client.rb +3 -0
  32. data/lib/action_web_service/container/action_controller_container.rb +93 -0
  33. data/lib/action_web_service/container/delegated_container.rb +86 -0
  34. data/lib/action_web_service/container/direct_container.rb +69 -0
  35. data/lib/action_web_service/container.rb +3 -0
  36. data/lib/action_web_service/dispatcher/abstract.rb +208 -0
  37. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +396 -0
  38. data/lib/action_web_service/dispatcher.rb +2 -0
  39. data/lib/action_web_service/invocation.rb +202 -0
  40. data/lib/action_web_service/protocol/abstract.rb +112 -0
  41. data/lib/action_web_service/protocol/discovery.rb +37 -0
  42. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +242 -0
  43. data/lib/action_web_service/protocol/soap_protocol.rb +176 -0
  44. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +123 -0
  45. data/lib/action_web_service/protocol.rb +4 -0
  46. data/lib/action_web_service/scaffolding.rb +281 -0
  47. data/lib/action_web_service/simple.rb +53 -0
  48. data/lib/action_web_service/string_to_datetime_for_soap.rb +16 -0
  49. data/lib/action_web_service/struct.rb +68 -0
  50. data/lib/action_web_service/support/class_inheritable_options.rb +26 -0
  51. data/lib/action_web_service/support/signature_types.rb +261 -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 +110 -0
  57. data/lib/action_web_service/version.rb +9 -0
  58. data/lib/action_web_service.rb +60 -0
  59. data/lib/actionwebservice.rb +1 -0
  60. data/setup.rb +1379 -0
  61. data/test/abstract_client.rb +184 -0
  62. data/test/abstract_dispatcher.rb +549 -0
  63. data/test/abstract_unit.rb +43 -0
  64. data/test/actionwebservice_unittest.db +0 -0
  65. data/test/api_test.rb +102 -0
  66. data/test/apis/auto_load_api.rb +3 -0
  67. data/test/apis/broken_auto_load_api.rb +2 -0
  68. data/test/base_test.rb +42 -0
  69. data/test/casting_test.rb +95 -0
  70. data/test/client_soap_test.rb +156 -0
  71. data/test/client_xmlrpc_test.rb +154 -0
  72. data/test/container_test.rb +75 -0
  73. data/test/debug.log +12305 -0
  74. data/test/dispatcher_action_controller_soap_test.rb +139 -0
  75. data/test/dispatcher_action_controller_xmlrpc_test.rb +59 -0
  76. data/test/fixtures/db_definitions/mysql.sql +8 -0
  77. data/test/fixtures/db_definitions/sqlite3.sql +8 -0
  78. data/test/fixtures/users.yml +12 -0
  79. data/test/gencov +3 -0
  80. data/test/invocation_test.rb +186 -0
  81. data/test/run +6 -0
  82. data/test/scaffolded_controller_test.rb +147 -0
  83. data/test/struct_test.rb +84 -0
  84. data/test/test_invoke_test.rb +113 -0
  85. metadata +182 -0
@@ -0,0 +1,396 @@
1
+ require 'benchmark'
2
+ require 'builder/xmlmarkup'
3
+
4
+ module ActionWebService # :nodoc:
5
+ module Dispatcher # :nodoc:
6
+ module ActionController # :nodoc:
7
+ def self.included(base) # :nodoc:
8
+ class << base
9
+ include ClassMethods
10
+ alias_method_chain :inherited, :action_controller
11
+ end
12
+ base.class_eval do
13
+ alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
14
+ end
15
+ base.add_web_service_api_callback do |klass, api|
16
+ if klass.web_service_dispatching_mode == :direct
17
+ klass.class_eval 'def api; dispatch_web_service_request; end'
18
+ end
19
+ end
20
+ base.add_web_service_definition_callback do |klass, name, info|
21
+ if klass.web_service_dispatching_mode == :delegated
22
+ klass.class_eval "def #{name}; dispatch_web_service_request; end"
23
+ elsif klass.web_service_dispatching_mode == :layered
24
+ klass.class_eval 'def api; dispatch_web_service_request; end'
25
+ end
26
+ end
27
+ base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
28
+ end
29
+
30
+ module ClassMethods # :nodoc:
31
+ def inherited_with_action_controller(child)
32
+ inherited_without_action_controller(child)
33
+ child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
34
+ end
35
+ end
36
+
37
+ module InstanceMethods # :nodoc:
38
+ private
39
+ def dispatch_web_service_request
40
+ method = request.method.to_s.upcase
41
+ allowed_methods = self.class.web_service_api ? (self.class.web_service_api.allowed_http_methods || []) : [ :post ]
42
+ allowed_methods = allowed_methods.map{|m| m.to_s.upcase }
43
+ if !allowed_methods.include?(method)
44
+ render :text => "#{method} not supported", :status=>500
45
+ return
46
+ end
47
+ exception = nil
48
+ begin
49
+ ws_request = discover_web_service_request(request)
50
+ rescue Exception => e
51
+ exception = e
52
+ end
53
+ if ws_request
54
+ ws_response = nil
55
+ exception = nil
56
+ bm = Benchmark.measure do
57
+ begin
58
+ ws_response = invoke_web_service_request(ws_request)
59
+ rescue Exception => e
60
+ exception = e
61
+ end
62
+ end
63
+ log_request(ws_request, request.raw_post)
64
+ if exception
65
+ log_error(exception) unless logger.nil?
66
+ send_web_service_error_response(ws_request, exception)
67
+ else
68
+ send_web_service_response(ws_response, bm.real)
69
+ end
70
+ else
71
+ exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
72
+ log_error(exception) unless logger.nil?
73
+ send_web_service_error_response(ws_request, exception)
74
+ end
75
+ rescue Exception => e
76
+ log_error(e) unless logger.nil?
77
+ send_web_service_error_response(ws_request, e)
78
+ end
79
+
80
+ def send_web_service_response(ws_response, elapsed=nil)
81
+ log_response(ws_response, elapsed)
82
+ options = { :type => ws_response.content_type, :disposition => 'inline' }
83
+ send_data(ws_response.body, options)
84
+ end
85
+
86
+ def send_web_service_error_response(ws_request, exception)
87
+ if ws_request
88
+ unless self.class.web_service_exception_reporting
89
+ exception = DispatcherError.new("Internal server error (exception raised)")
90
+ end
91
+ api_method = ws_request.api_method
92
+ public_method_name = api_method ? api_method.public_name : ws_request.method_name
93
+ return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
94
+ ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
95
+ send_web_service_response(ws_response)
96
+ else
97
+ if self.class.web_service_exception_reporting
98
+ message = exception.message
99
+ backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
100
+ else
101
+ message = "Exception raised"
102
+ backtrace = ""
103
+ end
104
+ render :status => 500, :text => "Internal protocol error: #{message}#{backtrace}"
105
+ end
106
+ end
107
+
108
+ def web_service_direct_invoke(invocation)
109
+ invocation.method_named_params.each do |name, value|
110
+ params[name] = value
111
+ end
112
+ web_service_direct_invoke_without_controller(invocation)
113
+ end
114
+
115
+ def log_request(ws_request, body)
116
+ unless logger.nil?
117
+ name = ws_request.method_name
118
+ api_method = ws_request.api_method
119
+ params = ws_request.method_params
120
+ if api_method && api_method.expects
121
+ params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
122
+ else
123
+ params = params.map{ |param| param.inspect }
124
+ end
125
+ service = ws_request.service_name
126
+ logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
127
+ logger.debug(indent(body))
128
+ end
129
+ end
130
+
131
+ def log_response(ws_response, elapsed=nil)
132
+ unless logger.nil?
133
+ elapsed = (elapsed ? " (%f):" % elapsed : ":")
134
+ logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
135
+ logger.debug(indent(ws_response.body))
136
+ end
137
+ end
138
+
139
+ def indent(body)
140
+ body.split(/\n/).map{|x| " #{x}"}.join("\n")
141
+ end
142
+ end
143
+
144
+ module WsdlAction # :nodoc:
145
+ XsdNs = 'http://www.w3.org/2001/XMLSchema'
146
+ WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
147
+ SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
148
+ SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
149
+ SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
150
+
151
+ def wsdl
152
+ case request.method
153
+ when :get
154
+ begin
155
+ options = { :type => 'text/xml', :disposition => 'inline' }
156
+ send_data(to_wsdl, options)
157
+ rescue Exception => e
158
+ log_error(e) unless logger.nil?
159
+ end
160
+ when :post
161
+ render :status => 500, :text => 'POST not supported'
162
+ end
163
+ end
164
+
165
+ private
166
+ def base_uri
167
+ host = request.host_with_port
168
+ relative_url_root = ::ActionController::Base.relative_url_root
169
+ scheme = request.ssl? ? 'https' : 'http'
170
+ '%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
171
+ end
172
+
173
+ def to_wsdl
174
+ xml = ''
175
+ inflect = web_service_inflect_type
176
+ dispatching_mode = web_service_dispatching_mode
177
+ global_service_name = wsdl_service_name
178
+ namespace = wsdl_namespace || 'urn:ActionWebService'
179
+ soap_action_base = "/#{controller_name}"
180
+
181
+ marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
182
+ apis = {}
183
+ case dispatching_mode
184
+ when :direct
185
+ api = self.class.web_service_api
186
+ web_service_name = controller_class_name.sub(/Controller$/, '').underscore
187
+ apis[web_service_name] = [api, register_api(api, marshaler)]
188
+ when :delegated, :layered
189
+ self.class.web_services.each do |web_service_name, info|
190
+ service = web_service_object(web_service_name)
191
+ api = service.class.web_service_api
192
+ apis[web_service_name] = [api, register_api(api, marshaler)]
193
+ end
194
+ end
195
+ custom_types = []
196
+ apis.values.each do |api, bindings|
197
+ bindings.each do |b|
198
+ custom_types << b unless custom_types.include?(b)
199
+ end
200
+ end
201
+
202
+ xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
203
+ xm.instruct!
204
+ xm.definitions('name' => wsdl_service_name,
205
+ 'targetNamespace' => namespace,
206
+ 'xmlns:typens' => namespace,
207
+ 'xmlns:xsd' => XsdNs,
208
+ 'xmlns:soap' => SoapNs,
209
+ 'xmlns:soapenc' => SoapEncodingNs,
210
+ 'xmlns:wsdl' => WsdlNs,
211
+ 'xmlns' => WsdlNs) do
212
+ # Generate XSD
213
+ if custom_types.size > 0
214
+ xm.types do
215
+ xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
216
+ simple_types, array_types, complex_types = [], [], []
217
+ custom_types.each do |binding|
218
+ case
219
+ when binding.type.simple? then simple_types.push binding
220
+ when binding.type.array? then array_types.push binding
221
+ when binding.type.structured? then complex_types.push binding
222
+ end
223
+ end
224
+ simple_types.each do |binding|
225
+ xm.xsd(:simpleType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
226
+ xm.xsd(:restriction, 'base' => "xsd:#{binding.type.base}") do
227
+ binding.type.restrictions do |name, value|
228
+ xm.xsd(name.to_sym, 'value' => value)
229
+ end
230
+ end
231
+ end
232
+ end
233
+ array_types.each do |binding|
234
+ xm.xsd(:complexType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
235
+ xm.xsd(:complexContent) do
236
+ xm.xsd(:restriction, 'base' => 'soapenc:Array') do
237
+ xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
238
+ 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
239
+ end
240
+ end
241
+ end
242
+ end
243
+ complex_types.each do |binding|
244
+ xm.xsd(:complexType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
245
+ xm.xsd(:all) do
246
+ binding.type.each_member do |name, type, options|
247
+ options ||= {}
248
+ b = marshaler.register_type(type)
249
+ xm.xsd(:element, {'name' => inflect ? name.to_s.camelize : name, 'type' => b.qualified_type_name('typens')}.merge(options))
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ # APIs
259
+ apis.each do |api_name, values|
260
+ api = values[0]
261
+ api.api_methods.each do |name, method|
262
+ gen = lambda do |msg_name, direction|
263
+ xm.message('name' => message_name_for(api_name, msg_name)) do
264
+ sym = nil
265
+ if direction == :out
266
+ returns = method.returns
267
+ if returns
268
+ binding = marshaler.register_type(returns[0])
269
+ xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
270
+ end
271
+ else
272
+ expects = method.expects
273
+ expects.each do |type|
274
+ binding = marshaler.register_type(type)
275
+ xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
276
+ end if expects
277
+ end
278
+ end
279
+ end
280
+ public_name = method.public_name
281
+ gen.call(public_name, :in)
282
+ gen.call("#{public_name}Response", :out)
283
+ end
284
+
285
+ # Port
286
+ port_name = port_name_for(global_service_name, api_name)
287
+ xm.portType('name' => port_name) do
288
+ api.api_methods.each do |name, method|
289
+ xm.operation('name' => method.public_name) do
290
+ xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
291
+ xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
292
+ end
293
+ end
294
+ end
295
+
296
+ # Bind it
297
+ binding_name = binding_name_for(global_service_name, api_name)
298
+ xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
299
+ xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
300
+ api.api_methods.each do |name, method|
301
+ xm.operation('name' => method.public_name) do
302
+ case web_service_dispatching_mode
303
+ when :direct
304
+ soap_action = soap_action_base + "/api/" + method.public_name
305
+ when :delegated, :layered
306
+ soap_action = soap_action_base \
307
+ + "/" + api_name.to_s \
308
+ + "/" + method.public_name
309
+ end
310
+ xm.soap(:operation, 'soapAction' => soap_action)
311
+ xm.input do
312
+ xm.soap(:body,
313
+ 'use' => 'encoded',
314
+ 'namespace' => namespace,
315
+ 'encodingStyle' => SoapEncodingNs)
316
+ end
317
+ xm.output do
318
+ xm.soap(:body,
319
+ 'use' => 'encoded',
320
+ 'namespace' => namespace,
321
+ 'encodingStyle' => SoapEncodingNs)
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
327
+
328
+ # Define it
329
+ xm.service('name' => "#{global_service_name}Service") do
330
+ apis.each do |api_name, values|
331
+ port_name = port_name_for(global_service_name, api_name)
332
+ binding_name = binding_name_for(global_service_name, api_name)
333
+ case web_service_dispatching_mode
334
+ when :direct, :layered
335
+ binding_target = 'api'
336
+ when :delegated
337
+ binding_target = api_name.to_s
338
+ end
339
+ xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
340
+ xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ def port_name_for(global_service, service)
348
+ "#{global_service}#{service.to_s.camelize}Port"
349
+ end
350
+
351
+ def binding_name_for(global_service, service)
352
+ "#{global_service}#{service.to_s.camelize}Binding"
353
+ end
354
+
355
+ def message_name_for(api_name, message_name)
356
+ mode = web_service_dispatching_mode
357
+ if mode == :layered || mode == :delegated
358
+ api_name.to_s + '-' + message_name
359
+ else
360
+ message_name
361
+ end
362
+ end
363
+
364
+ def register_api(api, marshaler)
365
+ bindings = {}
366
+ traverse_custom_types(api, marshaler, bindings) do |binding|
367
+ bindings[binding] = nil unless bindings.has_key?(binding)
368
+ element_binding = binding.element_binding
369
+ bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
370
+ end
371
+ bindings.keys
372
+ end
373
+
374
+ def traverse_custom_types(api, marshaler, bindings, &block)
375
+ api.api_methods.each do |name, method|
376
+ expects, returns = method.expects, method.returns
377
+ expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects
378
+ returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns
379
+ end
380
+ end
381
+
382
+ def traverse_type(marshaler, type, bindings, &block)
383
+ binding = marshaler.register_type(type)
384
+ return if bindings.has_key?(binding)
385
+ bindings[binding] = nil
386
+ yield binding
387
+ if type.array?
388
+ yield marshaler.register_type(type.element_type)
389
+ type = type.element_type
390
+ end
391
+ type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured?
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,2 @@
1
+ require 'action_web_service/dispatcher/abstract'
2
+ require 'action_web_service/dispatcher/action_controller_dispatcher'
@@ -0,0 +1,202 @@
1
+ module ActionWebService # :nodoc:
2
+ module Invocation # :nodoc:
3
+ class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
4
+ end
5
+
6
+ def self.included(base) # :nodoc:
7
+ base.extend(ClassMethods)
8
+ base.send(:include, ActionWebService::Invocation::InstanceMethods)
9
+ end
10
+
11
+ # Invocation interceptors provide a means to execute custom code before
12
+ # and after method invocations on ActionWebService::Base objects.
13
+ #
14
+ # When running in _Direct_ dispatching mode, ActionController filters
15
+ # should be used for this functionality instead.
16
+ #
17
+ # The semantics of invocation interceptors are the same as ActionController
18
+ # filters, and accept the same parameters and options.
19
+ #
20
+ # A _before_ interceptor can also cancel execution by returning +false+,
21
+ # or returning a <tt>[false, "cancel reason"]</tt> array if it wishes to supply
22
+ # a reason for canceling the request.
23
+ #
24
+ # === Example
25
+ #
26
+ # class CustomService < ActionWebService::Base
27
+ # before_invocation :intercept_add, :only => [:add]
28
+ #
29
+ # def add(a, b)
30
+ # a + b
31
+ # end
32
+ #
33
+ # private
34
+ # def intercept_add
35
+ # return [false, "permission denied"] # cancel it
36
+ # end
37
+ # end
38
+ #
39
+ # Options:
40
+ # [<tt>:except</tt>] A list of methods for which the interceptor will NOT be called
41
+ # [<tt>:only</tt>] A list of methods for which the interceptor WILL be called
42
+ module ClassMethods
43
+ # Appends the given +interceptors+ to be called
44
+ # _before_ method invocation.
45
+ def append_before_invocation(*interceptors, &block)
46
+ conditions = extract_conditions!(interceptors)
47
+ interceptors << block if block_given?
48
+ add_interception_conditions(interceptors, conditions)
49
+ append_interceptors_to_chain("before", interceptors)
50
+ end
51
+
52
+ # Prepends the given +interceptors+ to be called
53
+ # _before_ method invocation.
54
+ def prepend_before_invocation(*interceptors, &block)
55
+ conditions = extract_conditions!(interceptors)
56
+ interceptors << block if block_given?
57
+ add_interception_conditions(interceptors, conditions)
58
+ prepend_interceptors_to_chain("before", interceptors)
59
+ end
60
+
61
+ alias :before_invocation :append_before_invocation
62
+
63
+ # Appends the given +interceptors+ to be called
64
+ # _after_ method invocation.
65
+ def append_after_invocation(*interceptors, &block)
66
+ conditions = extract_conditions!(interceptors)
67
+ interceptors << block if block_given?
68
+ add_interception_conditions(interceptors, conditions)
69
+ append_interceptors_to_chain("after", interceptors)
70
+ end
71
+
72
+ # Prepends the given +interceptors+ to be called
73
+ # _after_ method invocation.
74
+ def prepend_after_invocation(*interceptors, &block)
75
+ conditions = extract_conditions!(interceptors)
76
+ interceptors << block if block_given?
77
+ add_interception_conditions(interceptors, conditions)
78
+ prepend_interceptors_to_chain("after", interceptors)
79
+ end
80
+
81
+ alias :after_invocation :append_after_invocation
82
+
83
+ def before_invocation_interceptors # :nodoc:
84
+ read_inheritable_attribute("before_invocation_interceptors")
85
+ end
86
+
87
+ def after_invocation_interceptors # :nodoc:
88
+ read_inheritable_attribute("after_invocation_interceptors")
89
+ end
90
+
91
+ def included_intercepted_methods # :nodoc:
92
+ read_inheritable_attribute("included_intercepted_methods") || {}
93
+ end
94
+
95
+ def excluded_intercepted_methods # :nodoc:
96
+ read_inheritable_attribute("excluded_intercepted_methods") || {}
97
+ end
98
+
99
+ private
100
+ def append_interceptors_to_chain(condition, interceptors)
101
+ write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
102
+ end
103
+
104
+ def prepend_interceptors_to_chain(condition, interceptors)
105
+ interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors")
106
+ write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors)
107
+ end
108
+
109
+ def extract_conditions!(interceptors)
110
+ return nil unless interceptors.last.is_a? Hash
111
+ interceptors.pop
112
+ end
113
+
114
+ def add_interception_conditions(interceptors, conditions)
115
+ return unless conditions
116
+ included, excluded = conditions[:only], conditions[:except]
117
+ write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included
118
+ write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded
119
+ end
120
+
121
+ def condition_hash(interceptors, *methods)
122
+ interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
123
+ end
124
+ end
125
+
126
+ module InstanceMethods # :nodoc:
127
+ def self.included(base)
128
+ base.class_eval do
129
+ alias_method_chain :perform_invocation, :interception
130
+ end
131
+ end
132
+
133
+ def perform_invocation_with_interception(method_name, params, &block)
134
+ return if before_invocation(method_name, params, &block) == false
135
+ return_value = perform_invocation_without_interception(method_name, params)
136
+ after_invocation(method_name, params, return_value)
137
+ return_value
138
+ end
139
+
140
+ def perform_invocation(method_name, params)
141
+ send(method_name, *params)
142
+ end
143
+
144
+ def before_invocation(name, args, &block)
145
+ call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
146
+ end
147
+
148
+ def after_invocation(name, args, result)
149
+ call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
150
+ end
151
+
152
+ private
153
+
154
+ def call_interceptors(interceptors, interceptor_args, &block)
155
+ if interceptors and not interceptors.empty?
156
+ interceptors.each do |interceptor|
157
+ next if method_exempted?(interceptor, interceptor_args[0].to_s)
158
+ result = case
159
+ when interceptor.is_a?(Symbol)
160
+ self.send(interceptor, *interceptor_args)
161
+ when interceptor_block?(interceptor)
162
+ interceptor.call(self, *interceptor_args)
163
+ when interceptor_class?(interceptor)
164
+ interceptor.intercept(self, *interceptor_args)
165
+ else
166
+ raise(
167
+ InvocationError,
168
+ "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
169
+ )
170
+ end
171
+ reason = nil
172
+ if result.is_a?(Array)
173
+ reason = result[1] if result[1]
174
+ result = result[0]
175
+ end
176
+ if result == false
177
+ block.call(reason) if block && reason
178
+ return false
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ def interceptor_block?(interceptor)
185
+ interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
186
+ end
187
+
188
+ def interceptor_class?(interceptor)
189
+ interceptor.respond_to?("intercept")
190
+ end
191
+
192
+ def method_exempted?(interceptor, method_name)
193
+ case
194
+ when self.class.included_intercepted_methods[interceptor]
195
+ !self.class.included_intercepted_methods[interceptor].include?(method_name)
196
+ when self.class.excluded_intercepted_methods[interceptor]
197
+ self.class.excluded_intercepted_methods[interceptor].include?(method_name)
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end