#-- # ============================================================================= # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * The names of its contributors may not be used to endorse or promote # products derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ============================================================================= #++ require 'copland/class-factory' require 'copland/errors' require 'copland/interceptor' require 'copland/interceptor-chain' require 'copland/models' require 'copland/utils' module Copland # A "service point" is the definition of a service. Just as a class describes # an object, a service point describes a service. Just as an object is the # instantiation of a class, so is a service the instantiation of a service # point. # # A service point consists of an "instantiator" (which describes _how_ the # service is to be instantiated) and a service model (which describes _when_ # the service is to be instantiated). Optionally, a service point may also # have _interceptors_ (which act as filters on the methods of a service), # _event producers_ (which send events to the service) and a _schema_ # (which is only useful for factory services, and describes what the format # of the parameters that the factory understands). class ServicePoint # This is the service model that will be used when no other has been # specified, and no other default was given in the options when the # service point was created. DEFAULT_SERVICE_MODEL = "singleton-deferred" # The package the owns this service point. attr_reader :owner # The unqualified name of this service point. attr_reader :name # An array of service points that instances of this service point will # listen to for events. attr_reader :event_producers # An array of Interceptor objects that should be instantiated when this # service is instantiated. attr_reader :interceptors # An (optional) description of this service point. attr_accessor :description # The instantiator that will be used to instantiate this service point. # (When a service point is first created, this is +nil+, and the service # point cannot be instantiated until an instantiator is specified.) attr_accessor :instantiator # The (optional) schema specification that identifies the valid parameters # that can be given to an instance of this service point. This only applies # when the service point describes a factory service, in which case the # schema will be used to validate and preprocess the parameters that are # passed to the factory when a new instance is required. attr_accessor :schema # Create a new service point, contained by the +owner+ package. The only # recognized option currently is :default_service_model, which # is used to identify the service model to use when no other service model # has been specified. def initialize( owner, name, opts={} ) @owner = owner @name = name use_service_model opts[ :default_service_model ] || DEFAULT_SERVICE_MODEL @event_producers = [] @interceptors = [] end # Returns the fully-qualified name of this service point, which will be the # name of the package and the name of the service point, separated by a # dot. def full_name owner.name + "." + name end # Return the service model instance that will be used to regulate the # instantiation of this service point. def service_model @model end # Specify the name of service model to use for this service point. An # instance of the corresponding service model will be looked up in the # class factory. def use_service_model( service_model_name ) @model = Copland::ClassFactory.instance.get( Copland::ServiceModel::POOL_NAME, service_model_name, self ) end # Add the given service point as an event producer for this service point. def add_event_producer( producer ) ( @event_producers << producer ).uniq! end # Add the given Interceptor object to this service point, to be # instantiated and applied when a new service is created. def add_interceptor( interceptor ) ( @interceptors << interceptor ).uniq! end # Returns the instance returned by the service model. This is the # preferred way of instantiating a service point, since it takes advantage # of the regulatory services offered by the service model. # # If a block is given, it will be used to do one-time initialization of # the new service. def instance( &block ) @model.instance( &block ) end # Directly instantiates the service point. This also applies the # interceptors and sends out notifications that the service has been # instantiated. # # This should never be called except by service model instances. If you # need to programmatically instantiate a service point, you should use the # #instance method; otherwise the service model will be bypassed. def instantiate( &init ) service = @instantiator.instantiate service.instance_eval &init if init # the service is registered with each of its event producers before # the interceptors are added, thus enabling the event notifications to # bypass the interceptor layer @event_producers.each do |producer| producer.instance.add_listener service end # Construct the interceptor layer around the service service = InterceptorChainBuilder.build( service, @interceptors ) # TODO: lifecycle notifications: "service_instantiated" return service end # Searches for (and instantiates) the service with the given name in the # registry, giving preference to services in this service point's package # (i.e., when an unqualified service name is given). If a block is # specified, it will be invoked when the package and service point name # have been resolved, allowing more than just services to be returned by # this method. def find_service( name, &block ) Copland::get_possibly_local_service( owner.registry, owner, name, &block ) end # Searches for the service point with the given name, giving preference to # service points within this service point's package (i.e., when an # unqualified service point name is given). If an invalid package name is # given, this will raise a PackageNotFound exception. If the named # service point does not exist, this will raise a ServicePointNotFound # exception. Otherwise, it will return the requested service point. def find_service_point( name ) point = find_service( name ) do |pkg, id| raise PackageNotFound, name unless pkg pkg.service_point( id ) end raise ServicePointNotFound, name unless point return point end # Adds a "pending" (i.e., unvalidated) interceptor definition to this # service point. This method is only valid before the service point has # been fixated (see #fixate!). The +definition+ parameter should be a hash # that contains the definition of the interceptor. The Interceptor itself # will be instantiated when the service point is fixated. def add_pending_interceptor( definition ) ( @pending_interceptors ||= [] ).push definition end # Fixates the service point. After calling this, #add_pending_interceptor # becomes illegal to call. # # Fixating a service point will cause its instantiator to be validated # (via the #validate! method of the corresponding instantiator). Also, any # pending interceptors will be processed and added to the service point. # Then, if the schema value that was associated with this service point is # a string value, then it is treated as a reference to an "external" # schema, which is then looked up. def fixate! extend Fixated if @schema.is_a?( String ) @schema = @owner.find_schema( @schema ) elsif !@schema.nil? @schema.fixate! end instantiator.validate! if @pending_interceptors @pending_interceptors.each do |definition| interceptor = Interceptor.new( self, definition ) add_interceptor interceptor end remove_instance_variable :@pending_interceptors end # do lazy evaluation of the actual event producer services, so that # no one is actually instantiated until needed. @event_producers.map! do |name| find_service_point( name ) end end # Returns +false+. Once the service point has been fixated, this method # will be overridden with a method that returns +true+. (See the # Fixated module). def fixated? false end # When a service point is fixated, it will extend this module. This # causes certain operations on the service point to become illegal, or # to do nothing. module Fixated # Raises DisallowedOperationException. def add_pending_interceptor( *args ) raise DisallowedOperationException, "cannot add pending interceptors to a fixated service point" end # Does nothing. def fixate! # does nothing end # Returns +true+. def fixated? true end end end end