package act.inject.genie; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import act.Act; import act.app.App; import act.app.AppClassLoader; import act.app.conf.AppConfigurator; import act.app.event.AppEventId; import act.controller.ActionMethodParamAnnotationHandler; import act.inject.*; import act.sys.Env; import act.util.*; import org.osgl.$; import org.osgl.Osgl; import org.osgl.exception.ConfigurationException; import org.osgl.exception.NotAppliedException; import org.osgl.inject.*; import org.osgl.inject.annotation.LoadValue; import org.osgl.inject.annotation.Provided; import org.osgl.mvc.annotation.Bind; import org.osgl.mvc.annotation.Param; import org.osgl.util.C; import org.osgl.util.E; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; public class GenieInjector extends DependencyInjectorBase<GenieInjector> { private static final Module SCOPE_MODULE = new Module() { @Override protected void configure() { bind(ScopeCache.SessionScope.class).to(new SessionScope()); bind(ScopeCache.RequestScope.class).to(new RequestScope()); bind(ScopeCache.SingletonScope.class).to(new SingletonScope()); } }; private volatile Genie genie; private Set<Object> modules; private Set<Class<? extends Annotation>> injectTags = new HashSet<Class<? extends Annotation>>(); public GenieInjector(App app) { super(app); modules = factories(); modules.add(SCOPE_MODULE); } @Override public <T> T get(Class<T> clazz) { return genie().get(clazz); } @Override public <T> Provider<T> getProvider(Class<T> aClass) { return genie().getProvider(aClass); } public <T> T get(BeanSpec spec) { return genie().get(spec); } @Override public synchronized void registerDiBinder(DependencyInjectionBinder binder) { super.registerDiBinder(binder); if (null != genie) { genie.registerProvider(binder.targetClass(), binder); } } @Override public boolean isProvided(Class<?> type) { return ActProviders.isProvided(type) || type.isAnnotationPresent(Provided.class) || type.isAnnotationPresent(Inject.class); } @Override public boolean isQualifier(Class<? extends Annotation> aClass) { return genie().isQualifier(aClass); } @Override public boolean isPostConstructProcessor(Class<? extends Annotation> aClass) { return genie().isPostConstructProcessor(aClass); } @Override public boolean isScope(Class<? extends Annotation> aClass) { return genie().isScope(aClass); } public void addModule(Object module) { E.illegalStateIf(null != genie); modules.add(module); } public boolean subjectToInject(BeanSpec spec) { return genie().subjectToInject(spec); } public boolean injectable(BeanSpec beanSpec) { Class rawType = beanSpec.rawType(); return (ActProviders.isProvided(rawType) || null != beanSpec.getAnnotation(Inject.class) || null != beanSpec.getAnnotation(Provided.class) || null != beanSpec.getAnnotation(Context.class) || null != beanSpec.getAnnotation(Singleton.class) || beanSpec.isInstanceOf(SingletonBase.class) || null != beanSpec.getAnnotation(ApplicationScoped.class) || subjectToInject(beanSpec) ); } private C.Set<Object> factories() { Set<String> factories = GenieFactoryFinder.factories(); int len = factories.size(); C.Set<Object> set = C.newSet(); if (0 == len) { return set; } ClassLoader cl = App.instance().classLoader(); for (String className : factories) { set.add($.classForName(className, cl)); } return set; } private Genie genie() { if (null == genie) { synchronized (this) { if (null == genie) { InjectListener listener = new GenieListener(this); genie = Genie.create(listener, modules.toArray(new Object[modules.size()])); for (Map.Entry<Class, DependencyInjectionBinder> entry : binders.entrySet()) { genie.registerProvider(entry.getKey(), entry.getValue()); } $.F2<Class, Provider, Void> register = new $.F2<Class, Provider, Void>() { @Override public Void apply(Class aClass, Provider provider) throws NotAppliedException, Osgl.Break { genie.registerProvider(aClass, provider); return null; } }; genie.registerQualifiers(Bind.class, Param.class); List<ActionMethodParamAnnotationHandler> list = Act.pluginManager().pluginList(ActionMethodParamAnnotationHandler.class); for (ActionMethodParamAnnotationHandler h : list) { Set<Class<? extends Annotation>> set = h.listenTo(); for (Class<? extends Annotation> c : set) { genie.registerQualifiers(c); } } ActProviders.registerBuiltInProviders(ActProviders.class, register); ActProviders.registerBuiltInProviders(GenieProviders.class, register); for (Class<? extends Annotation> injectTag : injectTags) { genie.registerInjectTag(injectTag); } genie.registerProvider(Genie.class, new Provider<Genie>() { @Override public Genie get() { return genie; } }); } } } return genie; } @SubClassFinder(callOn = AppEventId.DEPENDENCY_INJECTOR_LOADED) public static void foundModule(Class<? extends Module> moduleClass) { addModuleClass(moduleClass); } @SubClassFinder(callOn = AppEventId.DEPENDENCY_INJECTOR_LOADED) public static void foundConfigurator(Class<? extends AppConfigurator> configurator) { addModuleClass(configurator); } private static boolean hasBinding(Class<?> clazz) { GenieInjector gi = Act.injector(); Genie genie = gi.genie(); return (genie.hasProvider(clazz)); } @AnnotatedClassFinder(value = AutoBind.class, callOn = AppEventId.DEPENDENCY_INJECTOR_PROVISIONED, noAbstract = false) public static void foundAutoBinding(final Class<?> autoBinding) { // check if there are manual binding (via modules) for the class already if (hasBinding(autoBinding)) { return; } final AppClassLoader cl = Act.app().classLoader(); ClassInfoRepository repo = cl.classInfoRepository(); ClassNode root = repo.node(autoBinding.getName()); E.invalidConfigurationIf(null == root, "Cannot find AutoBind root: %s", autoBinding.getName()); final Set<Class<?>> candidates = new LinkedHashSet<>(); root.visitPublicNotAbstractSubTreeNodes(new $.Visitor<ClassNode>() { @Override public void visit(ClassNode classNode) throws Osgl.Break { try { Class<?> clazz = $.classForName(classNode.name(), cl); if (Env.matches(clazz)) { candidates.add(clazz); } } catch (ConfigurationException e) { throw e; } catch (RuntimeException e) { throw new ConfigurationException(e, "Unable to auto bind on %s", autoBinding.getName()); } } }); if (!candidates.isEmpty()) { GenieInjector injector = Act.app().injector(); // key is: set of annotations plus name Map<$.T2<Set<Annotation>, String>, Class<?>> multiCandidatesMap = new HashMap<>(); for (Class<?> c : candidates) { BeanSpec spec = BeanSpec.of(c, injector); Set<Annotation> qualifiers = spec.qualifiers(); String name = spec.name(); $.T2<Set<Annotation>, String> key = $.T2(qualifiers, name); if (multiCandidatesMap.containsKey(key)) { throw new ConfigurationException("Unable to auto bind on %s: multiple same qualified candidates found", autoBinding); } else { multiCandidatesMap.put(key, c); } } for (Map.Entry<$.T2<Set<Annotation>, String>, Class<?>> entry : multiCandidatesMap.entrySet()) { Genie.Binder binder = new Genie.Binder(autoBinding).to(entry.getValue()); $.T2<Set<Annotation>, String> key = entry.getKey(); Set<Annotation> qualifiers = key._1; String name = key._2; if (!qualifiers.isEmpty()) { binder = binder.withAnnotation(qualifiers.toArray(new Annotation[qualifiers.size()])); } if (null != name) { binder.named(name); } binder.register(injector.genie()); } } else { App.LOGGER.warn("Unable to auto bind on %s: implementation not found", autoBinding); } } @AnnotatedClassFinder(value = ModuleTag.class, callOn = AppEventId.DEPENDENCY_INJECTOR_LOADED, noAbstract = false) public static void foundTaggedModule(Class<?> taggedModuleClass) { addModuleClass(taggedModuleClass); } public static void addModuleClass(Class<?> moduleClass) { if (!isModuleAllowed(moduleClass)) { return; } App app = App.instance(); GenieInjector genieInjector = app.injector(); genieInjector.addModule(moduleClass); } @AnnotatedClassFinder(value = LoadValue.class, noAbstract = false, callOn = AppEventId.DEPENDENCY_INJECTOR_LOADED) public static void foundValueLoader(Class<? extends Annotation> valueLoader) { App app = App.instance(); GenieInjector genieInjector = app.injector(); genieInjector.injectTags.add(valueLoader); } @SubClassFinder public static void foundGenericTypedBeanLoader(Class<? extends GenericTypedBeanLoader> loaderClass) { App app = App.instance(); GenieInjector genieInjector = app.injector(); Type[] ta = loaderClass.getGenericInterfaces(); for (Type t : ta) { if (t instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) t; if (GenericTypedBeanLoader.class == pt.getRawType()) { Type compoentType = pt.getActualTypeArguments()[0]; genieInjector.genie().registerGenericTypedBeanLoader((Class) compoentType, app.getInstance(loaderClass)); } } } } @SubClassFinder public static void foundProviderBase(Class<? extends ActProvider> providerClass) { App app = App.instance(); GenieInjector genieInjector = app.injector(); ActProvider provider = app.getInstance(providerClass); genieInjector.genie().registerProvider(provider.targetType(), provider); } private static boolean isModuleAllowed(Class<?> moduleClass) { Env.Profile profile = moduleClass.getAnnotation(Env.Profile.class); if (null != profile) { return Env.matches(profile); } Env.Mode mode = moduleClass.getAnnotation(Env.Mode.class); if (null != mode) { return Env.matches(mode); } Env.Group group = moduleClass.getAnnotation(Env.Group.class); if (null != group) { return Env.matches(group); } return true; } }