#--
# =============================================================================
# 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