module ActionService # :nodoc: module Container # :nodoc: class ContainerError < ActionService::ActionServiceError # :nodoc: end def self.append_features(base) # :nodoc: super base.class_inheritable_option(:service_dispatching_mode, :direct) base.class_inheritable_option(:service_exception_reporting, true) base.extend(ClassMethods) base.send(:include, ActionService::Container::InstanceMethods) end module ClassMethods # Declares a service that will provides access to the API of the given # service +object+. +object+ must be an ActionService::Base derivative. # # Service object creation can either be _immediate_, where the object # instance is given at class definition time, or _deferred_, where # object instantiation is delayed until request time. # # ==== Immediate service object example # # class ApiController < ApplicationController # service_dispatching_mode :delegated # # service :person, PersonService.new # end # # For deferred instantiation, a block should be given instead of an # object instance. This block will be executed in controller instance # context, so it can rely on controller instance variables being present. # # ==== Deferred service object example # # class ApiController < ApplicationController # service_dispatching_mode :delegated # # service(:person) { PersonService.new(@request.env) } # end def service(name, object=nil, &block) if (object && block_given?) || (object.nil? && block.nil?) raise(ContainerError, "either service, or a block must be given") end name = name.to_sym if block_given? info = { name => { :block => block } } else info = { name => { :object => object } } end write_inheritable_hash("action_services", info) call_service_definition_callbacks(self, name, info) end # Whether this service contains a service with the given +name+ def has_service?(name) services.has_key?(name.to_sym) end def services # :nodoc: read_inheritable_attribute("action_services") || {} end def add_service_definition_callback(&block) # :nodoc: write_inheritable_array("service_definition_callbacks", [block]) end private def call_service_definition_callbacks(container_class, service_name, service_info) (read_inheritable_attribute("service_definition_callbacks") || []).each do |block| block.call(container_class, service_name, service_info) end end end module InstanceMethods # :nodoc: def service_object(service_name) info = self.class.services[service_name.to_sym] unless info raise(ContainerError, "no such service '#{service_name}'") end service = info[:block] service ? instance_eval(&service) : info[:object] end private def dispatch_service_request(protocol_request) case service_dispatching_mode when :direct dispatch_direct_service_request(protocol_request) when :delegated dispatch_delegated_service_request(protocol_request) else raise(ContainerError, "unsupported dispatching mode '#{service_dispatching_mode}'") end end def dispatch_direct_service_request(protocol_request) public_method_name = protocol_request.public_method_name api = self.class.service_api method_name = api.api_method_name(public_method_name) block = nil expects = nil if method_name signature = api.api_methods[method_name] expects = signature[:expects] protocol_request.type = Protocol::CheckedMessage protocol_request.signature = expects protocol_request.return_signature = signature[:returns] else protocol_request.type = Protocol::UncheckedMessage system_methods = self.class.read_inheritable_attribute('default_system_methods') || {} protocol = protocol_request.protocol block = system_methods[protocol.class] unless block method_name = api.default_api_method unless method_name && respond_to?(method_name) raise(ContainerError, "no such method ##{public_method_name}") end end end @method_params = protocol_request.unmarshal @params ||= {} if expects (1..@method_params.size).each do |i| i -= 1 if expects[i].is_a?(Hash) @params[expects[i].keys.shift.to_s] = @method_params[i] else @params["param#{i}"] = @method_params[i] end end end if respond_to?(:before_action) @params['action'] = method_name.to_s return protocol_request.marshal(nil) if before_action == false end perform_invoke = lambda do if block block.call(public_method_name, self.class, *@method_params) else send(method_name) end end try_default = true result = nil catch(:try_default) do result = perform_invoke.call try_default = false end if try_default method_name = api.default_api_method if method_name protocol_request.type = Protocol::UncheckedMessage else raise(ContainerError, "no such method ##{public_method_name}") end result = perform_invoke.call end after_action if respond_to?(:after_action) protocol_request.marshal(result) end def dispatch_delegated_service_request(protocol_request) service_name = protocol_request.service_name service = service_object(service_name) api = service.class.service_api public_method_name = protocol_request.public_method_name method_name = api.api_method_name(public_method_name) invocation = ActionService::Invocation::InvocationRequest.new( ActionService::Invocation::ConcreteInvocation, public_method_name, method_name) if method_name protocol_request.type = Protocol::CheckedMessage signature = api.api_methods[method_name] protocol_request.signature = signature[:expects] protocol_request.return_signature = signature[:returns] invocation.params = protocol_request.unmarshal else protocol_request.type = Protocol::UncheckedMessage invocation.type = ActionService::Invocation::VirtualInvocation system_methods = self.class.read_inheritable_attribute('default_system_methods') || {} protocol = protocol_request.protocol block = system_methods[protocol.class] if block invocation.block = block invocation.block_params << service.class else method_name = api.default_api_method if method_name && service.respond_to?(method_name) invocation.params = protocol_request.unmarshal invocation.method_name = method_name.to_sym else raise(ContainerError, "no such method /#{service_name}##{public_method_name}") end end end canceled_reason = nil canceled_block = lambda{|r| canceled_reason = r} perform_invoke = lambda do service.perform_invocation(invocation, &canceled_block) end try_default = true result = nil catch(:try_default) do result = perform_invoke.call try_default = false end if try_default method_name = api.default_api_method if method_name protocol_request.type = Protocol::UncheckedMessage invocation.params = protocol_request.unmarshal invocation.method_name = method_name.to_sym invocation.type = ActionService::Invocation::UnpublishedConcreteInvocation else raise(ContainerError, "no such method /#{service_name}##{public_method_name}") end result = perform_invoke.call end protocol_request.marshal(result) end end end end