# Instantiates beans according to their scopes class SmartIoC::BeanFactory include SmartIoC::Errors include SmartIoC::Args attr_reader :bean_file_loader def initialize(bean_definitions_storage, extra_package_contexts) @bean_definitions_storage = bean_definitions_storage @extra_package_contexts = extra_package_contexts @bean_file_loader = SmartIoC::BeanFileLoader.new @singleton_scope = SmartIoC::Scopes::Singleton.new @prototype_scope = SmartIoC::Scopes::Prototype.new @thread_scope = SmartIoC::Scopes::Request.new end def clear_scopes all_scopes.each(&:clear) end def force_clear_scopes all_scopes.each(&:force_clear) end # Get bean from the container by it's name, package, context # @param bean_name [Symbol] bean name # @param package [Symbol] package name # @param context [Symbol] context # @return bean instance # @raise [ArgumentError] if bean is not found # @raise [ArgumentError] if ambiguous bean definition was found def get_bean(bean_name, package: nil, context: nil) check_arg(bean_name, :bean_name, Symbol) check_arg(package, :package, Symbol) if package check_arg(context, :context, Symbol) if context get_or_build_bean(bean_name, package, context) end private def get_or_build_bean(bean_name, package, context, history = Set.new) @bean_file_loader.require_bean(bean_name) context = autodetect_context(bean_name, package, context) bean_definition = @bean_definitions_storage.find(bean_name, package, context) scope = get_scope(bean_definition) scope_bean = scope.get_bean(bean_definition.klass) is_recursive = history.include?(bean_name) history << bean_name if scope_bean && scope_bean.loaded update_dependencies(scope_bean.bean, bean_definition) scope_bean.bean else if is_recursive raise LoadRecursion.new(bean_definition) end beans_cache = init_bean_definition_cache(bean_definition) autodetect_bean_definitions_for_dependencies(bean_definition) preload_beans(bean_definition, beans_cache[bean_definition]) load_bean(bean_definition, beans_cache) end end def init_bean_definition_cache(bean_definition) { bean_definition => { scope_bean: nil, dependencies: { } } } end def update_dependencies(bean, bean_definition, updated_beans = {}) bean_definition.dependencies.each do |dependency| bd = autodetect_bean_definition( dependency.ref, dependency.package, bean_definition.package ) scope = get_scope(bean_definition) dep_bean = updated_beans[bd] if !dep_bean && scope_bean = scope.get_bean(bd.klass) dep_bean = scope_bean.bean end if !dep_bean dep_bean = get_or_build_bean(bd.name, bd.package, bd.context) bean.instance_variable_set(:"@#{dependency.bean}", dep_bean) if !scope.is_a?(SmartIoC::Scopes::Prototype) updated_beans[bd] = dep_bean end else update_dependencies(dep_bean, bd, updated_beans) end end end def autodetect_context(bean_name, package, context) return context if context if package @extra_package_contexts.get_context(package) else bean_definition = autodetect_bean_definition(bean_name, package, nil) bean_definition.context end end def autodetect_bean_definitions_for_dependencies(bean_definition) bean_definition.dependencies.each do |dependency| next if dependency.bean_definition @bean_file_loader.require_bean(dependency.ref) dependency.bean_definition = autodetect_bean_definition( dependency.ref, dependency.package, bean_definition.package ) autodetect_bean_definitions_for_dependencies(dependency.bean_definition) end end def autodetect_bean_definition(bean, package, parent_bean_package) if package bean_context = @extra_package_contexts.get_context(package) bds = @bean_definitions_storage.filter_by_with_drop_to_default_context(bean, package, bean_context) return bds.first if bds.size == 1 raise ArgumentError, "bean :#{bean} is not found in package :#{package}" end if parent_bean_package bean_context = @extra_package_contexts.get_context(parent_bean_package) bds = @bean_definitions_storage.filter_by_with_drop_to_default_context(bean, parent_bean_package, bean_context) return bds.first if bds.size == 1 end bds = @bean_definitions_storage.filter_by(bean) bds_by_package = bds.group_by(&:package) smart_bds = [] bds_by_package.each do |package, bd_list| # try to find bean definition with package context bd = bd_list.detect {|bd| bd.context == @extra_package_contexts.get_context(bd.package)} smart_bds << bd if bd # last try: find for :default context if !bd bd = bd_list.detect {|bd| bd.context == SmartIoC::Container::DEFAULT_CONTEXT} smart_bds << bd if bd end end if smart_bds.size > 1 raise ArgumentError, "Unable to autodetect bean :#{bean}.\nSeveral definitions were found.\n#{smart_bds.map(&:inspect).join("\n\n")}. Set package directly for injected dependency" end if smart_bds.size == 0 raise ArgumentError, "Unable to find bean :#{bean} in any package." end return smart_bds.first end def preload_beans(bean_definition, beans_cache) scope = get_scope(bean_definition) if scope_bean = scope.get_bean(bean_definition.klass) beans_cache[:scope_bean] = scope_bean else preload_bean_instance(bean_definition, beans_cache) end bean_definition.dependencies.each do |dependency| bd = dependency.bean_definition next if beans_cache[:dependencies].has_key?(bd) dep_bean_cache = init_bean_definition_cache(bd) beans_cache[:dependencies].merge!(dep_bean_cache) preload_beans(bd, dep_bean_cache[bd]) end end def preload_bean_instance(bean_definition, beans_cache) return if beans_cache[:scope_bean] scope = get_scope(bean_definition) scope_bean = scope.get_bean(bean_definition.klass) if scope_bean beans_cache[:scope_bean] = scope_bean return scope_bean end bean = if bean_definition.is_instance? bean_definition.klass.allocate else bean_definition.klass end scope_bean = SmartIoC::Scopes::Bean.new(bean, !bean_definition.has_factory_method?) scope.save_bean(bean_definition.klass, scope_bean) beans_cache[:scope_bean] = scope_bean scope_bean end def init_factory_bean(bean_definition, bd_opts) scope_bean = bd_opts[:scope_bean] if !scope_bean.loaded scope_bean.set_bean(scope_bean.bean.send(bean_definition.factory_method), true) end end def init_zero_dep_factory_beans(beans_cache) beans_cache.each do |bean_definition, bd_opts| if bean_definition.has_factory_method? has_factory_dependencies = !!bean_definition.dependencies.detect {|dep| dep.bean_definition.has_factory_method?} if bean_definition.dependencies.size == 0 || !has_factory_dependencies init_factory_bean(bean_definition, bd_opts) end end init_zero_dep_factory_beans(bd_opts[:dependencies]) if !bd_opts[:dependencies].empty? end end def collect_dependent_factory_beans(beans_cache, collection) beans_cache.each do |bean_definition, bd_opts| if bean_definition.has_factory_method? && bean_definition.dependencies.size > 0 collection << bean_definition end collect_dependent_factory_beans(bd_opts[:dependencies], collection) end collection end def init_dependent_factory_beans(beans_cache) dependent_factory_beans = collect_dependent_factory_beans(beans_cache, []) dependent_factory_beans.each do |bean_definition| cross_refference_bd = get_cross_refference(dependent_factory_beans, bean_definition) if cross_refference_bd has_factory_dependencies = !!cross_refference_bd.dependencies.detect {|dep| dep.bean_definition.has_factory_method?} if has_factory_dependencies raise ArgumentError, "Factory method beans should not cross refference each other. Bean :#{bean_definition.name} cross refferences bean :#{cross_refference_bd.name}." end end end beans_cache.each do |bean_definition, bd_opts| if bean_definition.has_factory_method? && bean_definition.dependencies.size > 0 inject_beans(bean_definition, bd_opts) init_factory_bean(bean_definition, bd_opts) end init_dependent_factory_beans(bd_opts[:dependencies]) end end def load_bean(bean_definition, beans_cache) init_zero_dep_factory_beans(beans_cache) init_dependent_factory_beans(beans_cache) inject_beans(bean_definition, beans_cache[bean_definition]) beans_cache[bean_definition][:scope_bean].bean end def inject_beans(bean_definition, beans_cache) bean = beans_cache[:scope_bean].bean bean_definition.dependencies.each do |dependency| bd = dependency.bean_definition dep_bean = beans_cache[:dependencies][bd][:scope_bean].bean bean.instance_variable_set(:"@#{dependency.bean}", dep_bean) inject_beans(bd, beans_cache[:dependencies][bd]) end end def get_cross_refference(refer_bean_definitions, current_bean_definition, seen_bean_definitions = []) current_bean_definition.dependencies.each do |dependency| bd = dependency.bean_definition next if seen_bean_definitions.include?(bd) if refer_bean_definitions.include?(bd) return bd end if crbd = get_cross_refference(refer_bean_definitions, bd, seen_bean_definitions + [bd]) return crbd end end nil end def all_scopes [@singleton_scope, @prototype_scope, @thread_scope] end def get_scope(bean_definition) case bean_definition.scope when SmartIoC::Scopes::Singleton::VALUE @singleton_scope when SmartIoC::Scopes::Prototype::VALUE @prototype_scope when SmartIoC::Scopes::Request::VALUE @thread_scope else raise ArgumentError, "bean definition for :#{bean_definition.name} has unsupported scope :#{bean_definition.scope}" end end end