# A ServiceStore is a collection of umlaut service definitions, with identifiers. # # There is one default global one that is typically used; originally this # was all neccesarily global state, but we refactored to be an actual object, # although there's still a default global one used, with class methods # that delegate to it, for backwards compat and convenience. # # By default, a ServiceStore loads service definitions from # Rails.root/config/umlaut_services.yml . Although in testing, # or for future architectural expansion, services can be manually supplied # instead. # # A ServiceStore instantiates services from definitions, by id, # ServiceStore.instantiate_service!("our_sfx") # # A ServiceStore's cached service definitions can be reset, useful in # testing: ServiceStore.reset! # # They'll then be lazily reloaded on next access, unless manually set. class ServiceStore @@global_service_store = ServiceStore.new def self.global_service_store @@global_service_store end # certain class methods all default to global default ServiceStore, # for global ServiceStore. For convenience, and backwards-compat. [ :config, :"config=", :service_definitions, :service_definition_for, :'instantiate_service!', :'reset!' ].each do |method| self.define_singleton_method(method) do |*args| global_service_store.send(method, *args) end end # Returns complete hash loaded from config/umlaut_services.yml # Passes through ERB first, allowing ERB in umlaut_services.yml def config # cache hash loaded from YAML, ensure it has the keys we expect. unless defined? @services_config_list yaml_path = File.expand_path("config/umlaut_services.yml", Rails.root) @services_config_list = if File.exists? yaml_path YAML::load(ERB.new(File.open(yaml_path).read).result) else {} end @services_config_list["default"] ||= {} @services_config_list["default"]["services"] ||= {} end return @services_config_list end # Manually set a config hash, as would normally be found serialized # in config/umlaut_services.yml. Useful in testing. All keys # should be strings!! # # Needs to have the somewhat cumbersome expected structure: # ["default"]["services"] => { "service_id" => definition_hash } def config=(hash) reset! @services_config_list = hash end # Returns hash keyed by unique service name, value service definition # hash. def service_definitions unless defined? @service_definitions @service_definitions = {} config.each_pair do |group_name, group| if group["services"] # Add the group name to each service # in the group group["services"].each_pair do |service_id, service| service["group"] = group_name end # Merge the group's services into the service definitions. @service_definitions.merge!( group["services"] ) end end # set service_id key in each based on hash key @service_definitions.each_pair do |key, hash| hash["service_id"] = key end end return @service_definitions end # Reset cached service definitions. They'll be lazily loaded when asked for, # typically by being looked up from disk again. Typically used for testing. def reset! remove_instance_variable "@service_definitions" if defined? @service_definitions remove_instance_variable "@services_config_list" if defined? @services_config_list end def service_definition_for(service_id) return service_definitions[service_id] end # pass in array of service group ids. eg. ["group1", "-group2"] # # Returns a list of service definition hashes. # # Start with default group(s). Remove any that are mentioned with "-group_id" in # the group list, add in any that are mentioned with "group_id" def determine_services(specified_groups = []) services = {} activated_service_groups = self.config.select do |group_id, group_definition| ((group_id == "default" || group_definition["default"] == true) || specified_groups.include?(group_id)) && ! specified_groups.include?("-#{group_id}") end activated_service_groups.each_pair do |group_id, group_definition| services.merge! (group_definition["services"] || {}) end # Remove any disabled services services.reject! {|service_id, hash| hash && hash["disabled"] == true} return services end # pass in string unique key OR a service definition hash, # and a current UmlautRequest. # get back instantiated Service object. # # If string service_id is passed in, but is not defined in application services, # a ServiceStore::NoSuchService exception will be raised. def instantiate_service!(service, request) definition = service.kind_of?(Hash) ? service : service_definition_for(service.to_s) raise NoSuchService.new("Service '#{service}'' does not exist in umlaut-services.yml") if definition.nil? className = definition["type"] || definition["service_id"] classConst = Kernel.const_get(className) service = classConst.new(definition) service.request = request return service end class NoSuchService < RuntimeError ; end end