#-- # ============================================================================= # 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/event-producer' require 'copland/instantiator' require 'copland/log-factory' require 'copland/models' require 'copland/package' require 'copland/service-point' require 'copland/utils' require 'copland/configuration/loader' require 'copland/configuration/yaml/loader' module Copland # The Registry is the primary interface exported by Copland. Very rarely # should client applications need to access any other interface directly. # # The Registry can be constructed in two ways. The first (and most common) is # to call the #build method, which instantiates, initializes, and returns the # new Registry instance in one step. # # registry = Registry.build # # The second is the instantiate an empty Registry and initialize it yourself. # This is a more arduous approach, but may be necessary for some more complex # Registry requirements. class Registry include EventProducer # Instantiate, initialize, and return a new Registry instance. The accepted # parameters are: # # * if the first parameter is a String, it is interpreted to be the # "default" search path. (This defaults to ".".) # * the other parameter (or the first parameter, if it is not a String) # must be either absent, or it must be a Hash, in which case it specifies # options that will be used to construct and initialize the Registry. # # Valid options are: # # * :default_service_model: the default service model to use when # creating service points, when an explicit service model has not been # given. # * :search_paths: an array of paths that should be searched for # package descriptors. # # Also, any option that LogFactory accepts is also valid, prefixed with # "log_". (I.e., :log_device instead of :device.) def self.build( *args ) if args.length > 2 raise ArgumentError, "expected [0..2] arguments, got #{args.length}" end default_search_path = "." options = {} default_search_path = args.shift if args.first.is_a? String options = args.shift if args.first.is_a? Hash unless args.empty? raise ArgumentError, "unexpected argument type #{args.first.class}" end ( options[:search_paths] ||= [] ).unshift( default_search_path ).uniq! options[:search_paths].compact! search_paths = options[:search_paths] options.delete :search_paths registry = new( options ) loader = Copland::Configuration::Loader.new( search_paths ) loader.add_loader Copland::Configuration::YAML::Loader.new( registry, loader ) loader.load( options ) startup = registry.service( "copland.Startup" ) startup.start return registry end # The LogFactory instance that will be employed by this registry for # creating logger instances. attr_reader :logs # Create a new, empty registry. The +options+ hash accepts any value that # LogFactory's constructor accepts, with "log_" prefixed. (I.e., # :log_device instead of :device.) def initialize( options={} ) @packages = Hash.new log_options = Hash.new options.each do |k,v| log_options[$1.intern] = v if k.to_s =~ /^log_(.*)/ end @logs = LogFactory.new( log_options ) end # Fixates the registry. This performs one-time operations on the registry, # and then makes it illegal to perform those operations subsequently on the # same registry instance. # # This will create the "copland.Registry" service point, fixate each of the # registry's packages, and then register a finalizer in ObjectSpace that # will cause the registry to always be gracefully shutdown. def fixate! define_self_as_service define_log_factory_as_service @packages.each { |id, pkg| pkg.fixate! } ObjectSpace.define_finalizer( self, proc { shutdown } ) extend Fixated end # Returns +false+. After the registry has been fixated (see #fixate!), this # will be overridden with a method that returns +true+. def fixated? false end # Shuts down the registry by notifying any interested listeners (using the # :registry_shutdown event), closing the logs, and then # extending the Shutdown module. def shutdown fire_event( :registry_shutdown ) @logs.close extend Shutdown end # Returns +false+. After the registry has been #shutdown, this will be be # overridden with a method that returns +true+. def shutdown? false end # This creates a new service point called "copland.Registry" and makes it # so that it always returns the active registry instance when instantiated. def define_self_as_service description = "This service is intended to allow services to " + "obtain a reference to the registry which created them. In general, " + "however, services should never need to reference the registry " + "directly; instead, they should rely on their descriptors to " + "specify their dependencies. There are a few exceptions to this " + "rule, though, and it is for those exceptions that this service is " + "made available." add_identity_service "copland", "Registry", self, description end private :define_self_as_service # This creates a new service point called "copland.LogFactory" and makes it # so that it always returns the log factory instance owned by the active # registry. def define_log_factory_as_service description = "The LogFactory service provides log instances " + "on demand. It is always an identity for the log factory instance " + "owned by the registry." add_identity_service "copland", "LogFactory", logs, description end private :define_self_as_service # A convenience method for clients that wish to add a specific object to # the registry as an "identity" service (that is, a singleton service point # that always instantiates as the given object). def add_identity_service( package_name, service_point_name, object, description=nil ) # begin owner = package( package_name ) if owner.nil? owner = Package.new( self, package_name ) add_package owner end service_point = ServicePoint.new( owner, service_point_name ) service_point.use_service_model "singleton" service_point.description = description service_point.instantiator = ClassFactory.instance.get( Instantiator::POOL_NAME, "identity", service_point, object ) owner.add_service_point( service_point ) service_point end # Returns the package with the given name. If there is no such package, # this returns +nil+. def package( name ) @packages[ name ] end # Returns the service with the given name. If no such service point can # be found, this raises the ServicePointNotFound exception. (See # Package#service.) # # If a block is given, it is passed to Package#service and is used to # initialize the service. def service( name, &init ) Copland::get_possibly_local_service( self, nil, name ) do |pkg, id| unless pkg raise ServicePointNotFound, "A fully-qualified service name must be given to Registry#service" else return pkg.service( id, &init ) end end end # Returns the service point with the given name. If no such service point # can be found, this +nil+. def service_point( name ) Copland::get_possibly_local_service( self, nil, name ) do |pkg, id| unless pkg raise ServicePointNotFound, "A fully-qualified service name must be given to Registry#service_point" else return pkg.service_point( id ) end end end # Returns +true+ if the named service exists in this registry, and +false+ # otherwise. def service_exist?( name ) Copland::get_possibly_local_service( self, nil, name ) do |pkg, id| unless pkg return false else return pkg.service_exist?( id ) end end end # This returns the configuration point with the given name. If the # package exists but no configuration point by that name can be # found in it, this returns +nil+. If the package itself does not # exist, then a ConfigurationPointNotFound exception is raised. def configuration_point( name ) Copland::get_possibly_local_service( self, nil, name ) do |pkg, id| unless pkg raise ConfigurationPointNotFound, "A fully-qualified configuration point name must be given " + "to Registry#service" else return pkg.configuration_point( id ) end end end # Adds the given package to the registry. If there is already a package # in the registry by that name, a DuplicatePackageError will be raised. def add_package( pkg ) if @packages[ pkg.name ] raise DuplicatePackageError, pkg.name end @packages[ pkg.name ] = pkg end # Iterates over each package in the registry. def each_package( &block ) @packages.each_value( &block ) self end # Returns the number of packages in the registry. def package_count @packages.size end # When Registry#fixate! is called for the first time, it will extend this # module. module Fixated # Does nothing def fixate! # do nothing end # Returns +true+. def fixated? true end end # When Registry#shutdown is called for the first time, it will extend this # module. This will make several operations illegal, like querying a # service. module Shutdown # Does nothing. def shutdown( *args ) # do nothing end # Returns +true+. def shutdown? true end # Raises a DisallowedOperationException. def package( *args ) raise DisallowedOperationException, "cannot get package from registry after shutdown" end # Raises a DisallowedOperationException. def service( *args ) raise DisallowedOperationException, "cannot get service from registry after shutdown" end # Raises a DisallowedOperationException. def configuration_point( *args ) raise DisallowedOperationException, "cannot get configuration point from registry after shutdown" end # Raises a DisallowedOperationException. def add_package( *args ) raise DisallowedOperationException, "cannot add package to registry after shutdown" end end end end