1 module ActionWebService # :nodoc:
2 module Invocation # :nodoc:
3 class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
6 def self.included(base) # :nodoc:
7 base.extend(ClassMethods)
8 base.send(:include, ActionWebService::Invocation::InstanceMethods)
11 # Invocation interceptors provide a means to execute custom code before
12 # and after method invocations on ActionWebService::Base objects.
14 # When running in _Direct_ dispatching mode, ActionController filters
15 # should be used for this functionality instead.
17 # The semantics of invocation interceptors are the same as ActionController
18 # filters, and accept the same parameters and options.
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.
26 # class CustomService < ActionWebService::Base
27 # before_invocation :intercept_add, :only => [:add]
35 # return [false, "permission denied"] # cancel it
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
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)
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)
61 alias :before_invocation :append_before_invocation
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)
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)
81 alias :after_invocation :append_after_invocation
83 def before_invocation_interceptors # :nodoc:
84 read_inheritable_attribute("before_invocation_interceptors")
87 def after_invocation_interceptors # :nodoc:
88 read_inheritable_attribute("after_invocation_interceptors")
91 def included_intercepted_methods # :nodoc:
92 read_inheritable_attribute("included_intercepted_methods") || {}
95 def excluded_intercepted_methods # :nodoc:
96 read_inheritable_attribute("excluded_intercepted_methods") || {}
100 def append_interceptors_to_chain(condition, interceptors)
101 write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
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)
109 def extract_conditions!(interceptors)
110 return nil unless interceptors.last.is_a? Hash
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
121 def condition_hash(interceptors, *methods)
122 interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
126 module InstanceMethods # :nodoc:
127 def self.included(base)
129 alias_method_chain :perform_invocation, :interception
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)
140 def perform_invocation(method_name, params)
141 send(method_name, *params)
144 def before_invocation(name, args, &block)
145 call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
148 def after_invocation(name, args, result)
149 call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
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)
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)
168 "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
172 if result.is_a?(Array)
173 reason = result[1] if result[1]
177 block.call(reason) if block && reason
184 def interceptor_block?(interceptor)
185 interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
188 def interceptor_class?(interceptor)
189 interceptor.respond_to?("intercept")
192 def method_exempted?(interceptor, method_name)
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)