diff options
Diffstat (limited to 'actionservice/lib/action_service/invocation.rb')
-rw-r--r-- | actionservice/lib/action_service/invocation.rb | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/actionservice/lib/action_service/invocation.rb b/actionservice/lib/action_service/invocation.rb new file mode 100644 index 0000000000..f20b546c5a --- /dev/null +++ b/actionservice/lib/action_service/invocation.rb @@ -0,0 +1,252 @@ +module ActionService # :nodoc: + module Invocation # :nodoc: + ConcreteInvocation = :concrete + VirtualInvocation = :virtual + UnpublishedConcreteInvocation = :unpublished_concrete + + class InvocationError < ActionService::ActionServiceError # :nodoc: + end + + def self.append_features(base) # :nodoc: + super + base.extend(ClassMethods) + base.send(:include, ActionService::Invocation::InstanceMethods) + end + + # Invocation interceptors provide a means to execute custom code before + # and after method invocations on ActionService::Base objects. + # + # When running in _Direct_ dispatching mode, ActionController filters + # should be used for this functionality. + # + # The semantics of invocation interceptors are the same as ActionController + # filters, and accept the same parameters and options. + # + # A _before_ interceptor can also cancel execution by returning +false+, + # or returning a <tt>[false, "cancel reason"]</tt> array if it wishes to supply + # a reason for canceling the request. + # + # === Example + # + # class CustomService < ActionService::Base + # before_invocation :intercept_add, :only => [:add] + # + # def add(a, b) + # a + b + # end + # + # private + # def intercept_add + # return [false, "permission denied"] # cancel it + # end + # end + # + # Options: + # [<tt>:except</tt>] A list of methods for which the interceptor will NOT be called + # [<tt>:only</tt>] A list of methods for which the interceptor WILL be called + module ClassMethods + # Appends the given +interceptors+ to be called + # _before_ method invocation. + def append_before_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + append_interceptors_to_chain("before", interceptors) + end + + # Prepends the given +interceptors+ to be called + # _before_ method invocation. + def prepend_before_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + prepend_interceptors_to_chain("before", interceptors) + end + + alias :before_invocation :append_before_invocation + + # Appends the given +interceptors+ to be called + # _after_ method invocation. + def append_after_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + append_interceptors_to_chain("after", interceptors) + end + + # Prepends the given +interceptors+ to be called + # _after_ method invocation. + def prepend_after_invocation(*interceptors, &block) + conditions = extract_conditions!(interceptors) + interceptors << block if block_given? + add_interception_conditions(interceptors, conditions) + prepend_interceptors_to_chain("after", interceptors) + end + + alias :after_invocation :append_after_invocation + + def before_invocation_interceptors # :nodoc: + read_inheritable_attribute("before_invocation_interceptors") + end + + def after_invocation_interceptors # :nodoc: + read_inheritable_attribute("after_invocation_interceptors") + end + + def included_intercepted_methods # :nodoc: + read_inheritable_attribute("included_intercepted_methods") || {} + end + + def excluded_intercepted_methods # :nodoc: + read_inheritable_attribute("excluded_intercepted_methods") || {} + end + + private + def append_interceptors_to_chain(condition, interceptors) + write_inheritable_array("#{condition}_invocation_interceptors", interceptors) + end + + def prepend_interceptors_to_chain(condition, interceptors) + interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors") + write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors) + end + + def extract_conditions!(interceptors) + return nil unless interceptors.last.is_a? Hash + interceptors.pop + end + + def add_interception_conditions(interceptors, conditions) + return unless conditions + included, excluded = conditions[:only], conditions[:except] + write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included + write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded + end + + def condition_hash(interceptors, *methods) + interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})} + end + end + + module InstanceMethods # :nodoc: + def self.append_features(base) + super + base.class_eval do + alias_method :perform_invocation_without_interception, :perform_invocation + alias_method :perform_invocation, :perform_invocation_with_interception + end + end + + def perform_invocation_with_interception(invocation, &block) + return if before_invocation(invocation.method_name, invocation.params, &block) == false + result = perform_invocation_without_interception(invocation) + after_invocation(invocation.method_name, invocation.params, result) + result + end + + def perform_invocation(invocation) + if invocation.concrete? + unless self.respond_to?(invocation.method_name) && \ + self.class.service_api.has_api_method?(invocation.method_name) + raise InvocationError, "no such service method '#{invocation.method_name}'" + end + end + params = invocation.params + if invocation.concrete? || invocation.unpublished_concrete? + self.send(invocation.method_name, *params) + else + if invocation.block + params = invocation.block_params + params + invocation.block.call(invocation.public_method_name, *params) + else + self.send(invocation.method_name, *params) + end + end + end + + def before_invocation(name, args, &block) + call_interceptors(self.class.before_invocation_interceptors, [name, args], &block) + end + + def after_invocation(name, args, result) + call_interceptors(self.class.after_invocation_interceptors, [name, args, result]) + end + + private + + def call_interceptors(interceptors, interceptor_args, &block) + if interceptors and not interceptors.empty? + interceptors.each do |interceptor| + next if method_exempted?(interceptor, interceptor_args[0].to_s) + result = case + when interceptor.is_a?(Symbol) + self.send(interceptor, *interceptor_args) + when interceptor_block?(interceptor) + interceptor.call(self, *interceptor_args) + when interceptor_class?(interceptor) + interceptor.intercept(self, *interceptor_args) + else + raise( + InvocationError, + "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method" + ) + end + reason = nil + if result.is_a?(Array) + reason = result[1] if result[1] + result = result[0] + end + if result == false + block.call(reason) if block && reason + return false + end + end + end + end + + def interceptor_block?(interceptor) + interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1) + end + + def interceptor_class?(interceptor) + interceptor.respond_to?("intercept") + end + + def method_exempted?(interceptor, method_name) + case + when self.class.included_intercepted_methods[interceptor] + !self.class.included_intercepted_methods[interceptor].include?(method_name) + when self.class.excluded_intercepted_methods[interceptor] + self.class.excluded_intercepted_methods[interceptor].include?(method_name) + end + end + end + + class InvocationRequest # :nodoc: + attr_accessor :type + attr :public_method_name + attr_accessor :method_name + attr_accessor :params + attr_accessor :block + attr :block_params + + def initialize(type, public_method_name, method_name, params=nil) + @type = type + @public_method_name = public_method_name + @method_name = method_name + @params = params || [] + @block = nil + @block_params = [] + end + + def concrete? + @type == ConcreteInvocation ? true : false + end + + def unpublished_concrete? + @type == UnpublishedConcreteInvocation ? true : false + end + end + + end +end |