1 /**
2  * Contains the implementation of application context setup.
3  *
4  * Part of the Poodinis Dependency Injection framework.
5  *
6  * Authors:
7  *  Mike Bierlee, m.bierlee@lostmoment.com
8  * Copyright: 2014-2023 Mike Bierlee
9  * License:
10  *  This software is licensed under the terms of the MIT license.
11  *  The full terms of the license can be found in the LICENSE file.
12  */
13 
14 module poodinis.context;
15 
16 import poodinis.container : DependencyContainer;
17 import poodinis.registration : Registration, existingInstance;
18 import poodinis.factory : CreatesSingleton, InstanceFactoryParameters;
19 import poodinis.autowire : autowire;
20 
21 import std.traits : hasUDA, ReturnType;
22 
23 class ApplicationContext {
24     public void registerDependencies(shared(DependencyContainer) container) {
25     }
26 }
27 
28 /**
29 * A component annotation is used for specifying which factory methods produce components in
30 * an application context.
31 */
32 struct Component {
33 }
34 
35 /**
36 * This annotation allows you to specify by which super type the component should be registered. This
37 * enables you to use type-qualified alternatives for dependencies.
38 */
39 struct RegisterByType(Type) {
40     Type type;
41 }
42 
43 /**
44 * Components with the prototype registration will be scoped as dependencies which will create
45 * new instances every time they are resolved. The factory method will be called repeatedly.
46 */
47 struct Prototype {
48 }
49 
50 /**
51 * Register dependencies through an application context.
52 *
53 * An application context allows you to fine-tune dependency set-up and instantiation.
54 * It is mostly used for dependencies which come from an external library or when you don't
55 * want to use annotations to set-up dependencies in your classes.
56 */
57 public void registerContext(Context : ApplicationContext)(shared(DependencyContainer) container) {
58     auto context = new Context();
59     context.registerDependencies(container);
60     context.registerContextComponents(container);
61     container.register!(ApplicationContext, Context)().existingInstance(context);
62     autowire(container, context);
63 }
64 
65 public void registerContextComponents(ApplicationContextType : ApplicationContext)(
66     ApplicationContextType context, shared(DependencyContainer) container) {
67     foreach (memberName; __traits(allMembers, ApplicationContextType)) {
68         foreach (overload; __traits(getOverloads, ApplicationContextType, memberName)) {
69             static if (__traits(getProtection, overload) == "public" && hasUDA!(overload, Component)) {
70                 auto factoryMethod = &__traits(getMember, context, memberName);
71                 Registration registration = null;
72                 auto createsSingleton = CreatesSingleton.yes;
73 
74                 foreach (attribute; __traits(getAttributes, overload)) {
75                     static if (is(attribute == RegisterByType!T, T)) {
76                         registration = container.register!(typeof(attribute.type),
77                             ReturnType!factoryMethod);
78                     } else static if (__traits(isSame, attribute, Prototype)) {
79                         createsSingleton = CreatesSingleton.no;
80                     }
81                 }
82 
83                 if (registration is null) {
84                     registration = container.register!(ReturnType!factoryMethod);
85                 }
86 
87                 registration.instanceFactory.factoryParameters = InstanceFactoryParameters(
88                     registration.instanceType,
89                     createsSingleton,
90                     null,
91                     factoryMethod
92                 );
93             }
94         }
95     }
96 }