1 /**
2  * Contains functionality for autowiring dependencies using a dependency container.
3  *
4  * This module is used in a dependency container for autowiring dependencies when resolving them.
5  * You typically only need this module if you want inject dependencies into a class instance not
6  * managed by a dependency container.
7  *
8  * Part of the Poodinis Dependency Injection framework.
9  *
10  * Authors:
11  *  Mike Bierlee, m.bierlee@lostmoment.com
12  * Copyright: 2014-2022 Mike Bierlee
13  * License:
14  *  This software is licensed under the terms of the MIT license.
15  *  The full terms of the license can be found in the LICENSE file.
16  */
17 
18 module poodinis.autowire;
19 
20 import poodinis.container : DependencyContainer, PreDestroy, ResolveException, ResolveOption;
21 import poodinis.registration : Registration, InstantiationContext;
22 import poodinis.factory : InstanceFactory, InstanceFactoryParameters, CreatesSingleton;
23 import poodinis.valueinjection : ValueInjector, ValueInjectionException,
24     ValueNotAvailableException, Value, MandatoryValue;
25 import poodinis.altphobos : isFunction;
26 import poodinis.imports : createImportsString;
27 
28 import std.exception : enforce;
29 import std.string : format;
30 import std.traits : BaseClassesTuple, FieldNameTuple, fullyQualifiedName, hasUDA, isDynamicArray;
31 import std.range : ElementType;
32 
33 debug
34 {
35     import std.stdio : writeln;
36 }
37 
38 private struct UseMemberType
39 {
40 }
41 
42 /**
43  * UDA for annotating class members as candidates for autowiring.
44  *
45  * Optionally a template parameter can be supplied to specify the type of a qualified class. The qualified type
46  * of a concrete class is used to autowire members declared by supertype. If no qualifier is supplied, the type
47  * of the member is used as qualifier.
48  *
49  * Examples:
50  * Annotate member of class to be autowired:
51  * ---
52  * class Car {
53  *    @Autowire
54  *    public Engine engine;
55  * }
56  * ---
57  *
58  * Annotate member of class with qualifier:
59  * ---
60  * class FuelEngine : Engine { ... }
61  * class ElectricEngine : Engine { ... }
62  *
63  * class HybridCar {
64  *    @Autowire!FuelEngine
65  *    public Engine fuelEngine;
66  *
67  *    @Autowire!ElectricEngine
68  *    public Engine electricEngine;
69  * }
70  * ---
71  * The members of an instance of "HybridCar" will now be autowired properly, because the autowire mechanism will
72  * autowire member "fuelEngine" as if it's of type "FuelEngine". This means that the members of instance "fuelEngine"
73  * will also be autowired because the autowire mechanism knows that member "fuelEngine" is an instance of "FuelEngine"
74  */
75 struct Autowire(QualifierType)
76 {
77     QualifierType qualifier;
78 }
79 
80 /**
81  * UDA for marking autowired dependencies optional.
82  * Optional dependencies will not lead to a resolveException when there is no type registered for them.
83  * The member will remain null.
84  */
85 struct OptionalDependency
86 {
87 }
88 
89 /**
90  * UDA for annotating class members to be autowired with a new instance regardless of their registration scope.
91  *
92  * Examples:
93  *---
94  * class Car {
95  *     @Autowire
96  *     @AssignNewInstance
97  *     public Antenna antenna;
98  * }
99  *---
100  * antenna will always be assigned a new instance of class Antenna.
101  */
102 struct AssignNewInstance
103 {
104 }
105 
106 private void printDebugAutowiredInstance(TypeInfo instanceType, void* instanceAddress)
107 {
108     debug
109     {
110         writeln(format("DEBUG: Autowiring members of [%s@%s]", instanceType, instanceAddress));
111     }
112 }
113 
114 /**
115  * Autowires members of a given instance using dependencies registered in the given container.
116  *
117  * All members of the given instance, which are annotated using the "Autowire" UDA, are autowired.
118  * Members can have any visibility (public, private, etc). All members are resolved using the given
119  * container. Qualifiers are used to determine the type of class to resolve for any member of instance.
120  *
121  * See_Also: Autowire
122  */
123 public void autowire(Type)(shared(DependencyContainer) container, Type instance)
124 {
125     debug (poodinisVerbose)
126     {
127         printDebugAutowiredInstance(typeid(Type), &instance);
128     }
129 
130     // Recurse into base class if there are more between Type and Object in the hierarchy
131     static if (BaseClassesTuple!Type.length > 1)
132     {
133         autowire!(BaseClassesTuple!Type[0])(container, instance);
134     }
135 
136     foreach (index, name; FieldNameTuple!Type)
137     {
138         autowireMember!(name, index, Type)(container, instance);
139     }
140 }
141 
142 private void printDebugAutowiringCandidate(TypeInfo candidateInstanceType,
143         void* candidateInstanceAddress, TypeInfo instanceType, void* instanceAddress, string member)
144 {
145     debug
146     {
147         writeln(format("DEBUG: Autowired instance [%s@%s] to [%s@%s].%s", candidateInstanceType,
148                 candidateInstanceAddress, instanceType, instanceAddress, member));
149     }
150 }
151 
152 private void printDebugAutowiringArray(TypeInfo superTypeInfo,
153         TypeInfo instanceType, void* instanceAddress, string member)
154 {
155     debug
156     {
157         writeln(format("DEBUG: Autowired all registered instances of super type %s to [%s@%s].%s",
158                 superTypeInfo, instanceType, instanceAddress, member));
159     }
160 }
161 
162 private void autowireMember(string member, size_t memberIndex, Type)(
163         shared(DependencyContainer) container, Type instance)
164 {
165     foreach (attribute; __traits(getAttributes, Type.tupleof[memberIndex]))
166     {
167         static if (is(attribute == Autowire!T, T))
168         {
169             injectInstance!(member, memberIndex, typeof(attribute.qualifier))(container, instance);
170         }
171         else static if (__traits(isSame, attribute, Autowire))
172         {
173             injectInstance!(member, memberIndex, UseMemberType)(container, instance);
174         }
175         else static if (is(typeof(attribute) == Value))
176         {
177             enum key = attribute.key;
178             injectValue!(member, memberIndex, key, false)(container, instance);
179         }
180         else static if (is(typeof(attribute) == MandatoryValue))
181         {
182             enum key = attribute.key;
183             injectValue!(member, memberIndex, key, true)(container, instance);
184         }
185     }
186 }
187 
188 private void injectInstance(string member, size_t memberIndex, QualifierType, Type)(
189         shared(DependencyContainer) container, Type instance)
190 {
191     if (instance.tupleof[memberIndex] is null)
192     {
193         alias MemberType = typeof(Type.tupleof[memberIndex]);
194         enum isOptional = hasUDA!(Type.tupleof[memberIndex], OptionalDependency);
195 
196         static if (isDynamicArray!MemberType)
197         {
198             injectMultipleInstances!(member, memberIndex, isOptional, MemberType)(container,
199                     instance);
200         }
201         else
202         {
203             injectSingleInstance!(member, memberIndex, isOptional, MemberType, QualifierType)(container,
204                     instance);
205         }
206     }
207 }
208 
209 private void injectMultipleInstances(string member, size_t memberIndex,
210         bool isOptional, MemberType, Type)(shared(DependencyContainer) container, Type instance)
211 {
212     alias MemberElementType = ElementType!MemberType;
213     static if (isOptional)
214     {
215         auto instances = container.resolveAll!MemberElementType(ResolveOption.noResolveException);
216     }
217     else
218     {
219         auto instances = container.resolveAll!MemberElementType;
220     }
221 
222     instance.tupleof[memberIndex] = instances;
223     debug (poodinisVerbose)
224     {
225         printDebugAutowiringArray(typeid(MemberElementType), typeid(Type), &instance, member);
226     }
227 }
228 
229 private void injectSingleInstance(string member, size_t memberIndex,
230         bool isOptional, MemberType, QualifierType, Type)(
231         shared(DependencyContainer) container, Type instance)
232 {
233     debug (poodinisVerbose)
234     {
235         TypeInfo qualifiedInstanceType = typeid(MemberType);
236     }
237 
238     enum assignNewInstance = hasUDA!(Type.tupleof[memberIndex], AssignNewInstance);
239 
240     MemberType qualifiedInstance;
241     static if (!is(QualifierType == UseMemberType))
242     {
243         qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType,
244                 assignNewInstance, isOptional)(container);
245         debug (poodinisVerbose)
246         {
247             qualifiedInstanceType = typeid(QualifierType);
248         }
249     }
250     else
251     {
252         qualifiedInstance = createOrResolveInstance!(MemberType, MemberType,
253                 assignNewInstance, isOptional)(container);
254     }
255 
256     instance.tupleof[memberIndex] = qualifiedInstance;
257 
258     debug (poodinisVerbose)
259     {
260         printDebugAutowiringCandidate(qualifiedInstanceType,
261                 &qualifiedInstance, typeid(Type), &instance, member);
262     }
263 }
264 
265 private QualifierType createOrResolveInstance(MemberType, QualifierType,
266         bool createNew, bool isOptional)(shared(DependencyContainer) container)
267 {
268     static if (createNew)
269     {
270         auto instanceFactory = new InstanceFactory();
271         instanceFactory.factoryParameters = InstanceFactoryParameters(typeid(MemberType),
272                 CreatesSingleton.no);
273         return cast(MemberType) instanceFactory.getInstance();
274     }
275     else
276     {
277         static if (isOptional)
278         {
279             return container.resolve!(MemberType, QualifierType)(ResolveOption.noResolveException);
280         }
281         else
282         {
283             return container.resolve!(MemberType, QualifierType);
284         }
285     }
286 }
287 
288 private void injectValue(string member, size_t memberIndex, string key, bool mandatory, Type)(
289         shared(DependencyContainer) container, Type instance)
290 {
291     alias MemberType = typeof(Type.tupleof[memberIndex]);
292     try
293     {
294         auto injector = container.resolve!(ValueInjector!MemberType);
295         instance.tupleof[memberIndex] = injector.get(key);
296         debug (poodinisVerbose)
297         {
298             printDebugValueInjection(typeid(Type), &instance, member, typeid(MemberType), key);
299         }
300     }
301     catch (ResolveException e)
302     {
303         throw new ValueInjectionException(format(
304                 "Could not inject value of type %s into %s.%s: value injector is missing for this type.",
305                 typeid(MemberType), typeid(Type), member));
306     }
307     catch (ValueNotAvailableException e)
308     {
309         static if (mandatory)
310         {
311             throw new ValueInjectionException(format("Could not inject value of type %s into %s.%s",
312                     typeid(MemberType), typeid(Type), member), e);
313         }
314     }
315 }
316 
317 private void printDebugValueInjection(TypeInfo instanceType,
318         void* instanceAddress, string member, TypeInfo valueType, string key)
319 {
320     debug
321     {
322         writeln(format("DEBUG: Injected value with key '%s' of type %s into [%s@%s].%s",
323                 key, valueType, instanceType, instanceAddress, member));
324     }
325 }
326 
327 /**
328  * Autowire the given instance using the globally available dependency container.
329  *
330  * See_Also: DependencyContainer
331  * Deprecated: Using the global container is undesired. See DependencyContainer.getInstance().
332  */
333 public deprecated void globalAutowire(Type)(Type instance)
334 {
335     DependencyContainer.getInstance().autowire(instance);
336 }
337 
338 class AutowiredRegistration(RegistrationType : Object) : Registration
339 {
340     private shared(DependencyContainer) container;
341 
342     public this(TypeInfo registeredType, InstanceFactory instanceFactory,
343             shared(DependencyContainer) originatingContainer)
344     {
345         super(registeredType, typeid(RegistrationType), instanceFactory, originatingContainer);
346     }
347 
348     public override Object getInstance(
349             InstantiationContext context = new AutowireInstantiationContext())
350     {
351         enforce(!(originatingContainer is null),
352                 "The registration's originating container is null. There is no way to resolve autowire dependencies.");
353 
354         RegistrationType instance = cast(RegistrationType) super.getInstance(context);
355 
356         AutowireInstantiationContext autowireContext = cast(AutowireInstantiationContext) context;
357         enforce(!(autowireContext is null), "Given instantiation context type could not be cast to an AutowireInstantiationContext. If you relied on using the default assigned context: make sure you're calling getInstance() on an instance of type AutowiredRegistration!");
358         if (autowireContext.autowireInstance)
359         {
360             originatingContainer.autowire(instance);
361         }
362 
363         this.preDestructor = getPreDestructor(instance);
364 
365         return instance;
366     }
367 
368     private void delegate() getPreDestructor(RegistrationType instance)
369     {
370         void delegate() preDestructor = null;
371         foreach (memberName; __traits(allMembers, RegistrationType))
372         {
373             mixin(createImportsString!RegistrationType);
374             enum QualifiedName = fullyQualifiedName!RegistrationType ~ `.` ~ memberName;
375             static if (__traits(compiles, __traits(getProtection, __traits(getMember, instance, memberName)))
376                     && __traits(getProtection, __traits(getMember, instance, memberName)) == "public"
377                     && isFunction!(mixin(QualifiedName))
378                     && hasUDA!(__traits(getMember, instance, memberName), PreDestroy))
379             {
380                 preDestructor = &__traits(getMember, instance, memberName);
381             }
382         }
383 
384         return preDestructor;
385     }
386 }
387 
388 class AutowireInstantiationContext : InstantiationContext
389 {
390     public bool autowireInstance = true;
391 }