1 /**
2  * Contains the implementation of the dependency container.
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.container;
15 
16 import poodinis.registration : Registration, singleInstance,
17     toConcreteTypeListString, initializeFactoryType;
18 import poodinis.autowire : AutowiredRegistration, AutowireInstantiationContext;
19 import poodinis.factory : ConstructorInjectingInstanceFactory;
20 import poodinis.valueinjection : ValueInjectionException;
21 import poodinis.altphobos : isFunction;
22 import poodinis.imports : createImportsString;
23 
24 import std.string : format;
25 import std.algorithm : canFind;
26 import std.traits : fullyQualifiedName, hasUDA, BaseTypeTuple;
27 import std.meta : AliasSeq;
28 
29 debug (poodinisVerbose) {
30     import std.stdio : writeln;
31 }
32 
33 /**
34  * Exception thrown when errors occur while resolving a type in a dependency container.
35  */
36 class ResolveException : Exception {
37     this(string message, TypeInfo resolveType) {
38         super(format("Exception while resolving type %s: %s", resolveType.toString(), message));
39     }
40 
41     this(Throwable cause, TypeInfo resolveType) {
42         super(format("Exception while resolving type %s", resolveType.toString()), cause);
43     }
44 }
45 
46 /**
47  * Exception thrown when errors occur while registering a type in a dependency container.
48  */
49 class RegistrationException : Exception {
50     this(string message, TypeInfo registrationType) {
51         super(format("Exception while registering type %s: %s",
52                 registrationType.toString(), message));
53     }
54 }
55 
56 /**
57  * Options which influence the process of registering dependencies
58  */
59 public enum RegistrationOption {
60     none = 0,
61     /**
62      * Prevent a concrete type being registered on itself. With this option you will always need
63      * to use the supertype as the type of the dependency.
64      */
65     doNotAddConcreteTypeRegistration = 1 << 0,
66 }
67 
68 /**
69  * Options which influence the process of resolving dependencies
70  */
71 public enum ResolveOption {
72     none = 0,
73     /**
74      * Registers the type you're trying to resolve before returning it.
75      * This essentially makes registration optional for resolving by concerete types.
76      * Resolinvg will still fail when trying to resolve a dependency by supertype.
77      */
78     registerBeforeResolving = 1 << 0,
79 
80     /**
81      * Does not throw a resolve exception when a type is not registered but will
82      * return null instead. If the type is an array, an empty array is returned instead.
83      */
84     noResolveException = 1 << 1
85 }
86 
87 /**
88  * Methods marked with this UDA within dependencies are called after that dependency
89  * is constructed by the dependency container.
90  *
91  * Multiple methods can be marked and will all be called after construction. The order in which
92  * methods are called is undetermined. Methods should have the signature void(void).
93  */
94 struct PostConstruct {
95 }
96 
97 /**
98  * Methods marked with this UDA within dependencies are called before the container
99  * loses the dependency's registration.
100  *
101  * This method is called when removeRegistration or clearAllRegistrations is called.
102  * It will also be called when the container's destructor is called.
103  */
104 struct PreDestroy {
105 }
106 
107 /**
108  * The dependency container maintains all dependencies registered with it.
109  *
110  * Dependencies registered by a container can be resolved as long as they are still registered with the container.
111  * Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of
112  * dependencies are created. Resolved dependencies will be autowired before being returned.
113  *
114  * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies.
115  * You can still create new instances of this class for exceptional situations.
116  */
117 synchronized class DependencyContainer {
118     private Registration[][TypeInfo] registrations;
119 
120     private Registration[] autowireStack;
121 
122     private RegistrationOption persistentRegistrationOptions;
123     private ResolveOption persistentResolveOptions;
124 
125     ~this() {
126         clearAllRegistrations();
127     }
128 
129     /**
130      * Register a dependency by concrete class type.
131      *
132      * A dependency registered by concrete class type can only be resolved by concrete class type.
133      * No qualifiers can be used when resolving dependencies which are registered by concrete type.
134      *
135      * The default registration scope is "single instance" scope.
136      *
137      * Returns:
138      * A registration is returned which can be used to change the registration scope.
139      *
140      * Examples:
141      * Register and resolve a class by concrete type:
142      * ---
143      * class Cat : Animal { ... }
144      * container.register!Cat;
145      * ---
146      *
147      * See_Also: singleInstance, newInstance, existingInstance
148      */
149     public Registration register(ConcreteType)(RegistrationOption options = RegistrationOption.none) {
150         return register!(ConcreteType, ConcreteType)(options);
151     }
152 
153     /**
154      * Register a dependency by super type.
155      *
156      * A dependency registered by super type can only be resolved by super type. A qualifier is typically
157      * used to resolve dependencies registered by super type.
158      *
159      * The default registration scope is "single instance" scope.
160      *
161      * Examples:
162      * Register and resolve by super type
163      * ---
164      * class Cat : Animal { ... }
165      * container.register!(Animal, Cat);
166      * ---
167      *
168      * See_Also: singleInstance, newInstance, existingInstance, RegistrationOption
169      */
170     public Registration register(SuperType, ConcreteType:
171         SuperType)(RegistrationOption options = RegistrationOption.none)
172             if (!is(ConcreteType == struct)) {
173         TypeInfo registeredType = typeid(SuperType);
174         TypeInfo_Class concreteType = typeid(ConcreteType);
175 
176         debug (poodinisVerbose) {
177             writeln(format("DEBUG: Register type %s (as %s)",
178                     concreteType.toString(), registeredType.toString()));
179         }
180 
181         auto existingRegistration = getExistingRegistration(registeredType, concreteType);
182         if (existingRegistration) {
183             return existingRegistration;
184         }
185 
186         auto instanceFactory = new ConstructorInjectingInstanceFactory!ConcreteType(this);
187         auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType,
188             instanceFactory, this);
189         newRegistration.initializeFactoryType().singleInstance();
190 
191         static if (!is(SuperType == ConcreteType)) {
192             if (!hasOption(options, persistentRegistrationOptions,
193                     RegistrationOption.doNotAddConcreteTypeRegistration)) {
194                 auto concreteTypeRegistration = register!ConcreteType;
195                 concreteTypeRegistration.linkTo(newRegistration);
196             }
197         }
198 
199         registrations[registeredType] ~= cast(shared(Registration)) newRegistration;
200         return newRegistration;
201     }
202 
203     private bool hasOption(OptionType)(OptionType options,
204         OptionType persistentOptions, OptionType option) {
205         return ((options | persistentOptions) & option) != 0;
206     }
207 
208     private OptionType buildFlags(OptionType)(OptionType[] options) {
209         OptionType flags;
210         foreach (option; options) {
211             flags |= option;
212         }
213         return flags;
214     }
215 
216     private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) {
217         auto existingCandidates = registrationType in registrations;
218         if (existingCandidates) {
219             return getRegistration(cast(Registration[])*existingCandidates, qualifierType);
220         }
221 
222         return null;
223     }
224 
225     private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) {
226         foreach (existingRegistration; candidates) {
227             if (existingRegistration.instanceType == concreteType) {
228                 return existingRegistration;
229             }
230         }
231 
232         return null;
233     }
234 
235     /**
236      * Resolve dependencies.
237      *
238      * Dependencies can only resolved using this method if they are registered by concrete type or the only
239      * concrete type registered by super type.
240      *
241      * Resolved dependencies are automatically autowired before being returned.
242      *
243      * Returns:
244      * An instance is returned which is created according to the registration scope with which they are registered.
245      *
246      * Throws:
247      * ResolveException when type is not registered.
248      *
249      * Examples:
250      * Resolve dependencies registered by super type and concrete type:
251      * ---
252      * class Cat : Animal { ... }
253      * class Dog : Animal { ... }
254      *
255      * container.register!(Animal, Cat);
256      * container.register!Dog;
257      *
258      * container.resolve!Animal;
259      * container.resolve!Dog;
260      * ---
261      * You cannot resolve a dependency when it is registered by multiple super types:
262      * ---
263      * class Cat : Animal { ... }
264      * class Dog : Animal { ... }
265      *
266      * container.register!(Animal, Cat);
267      * container.register!(Animal, Dog);
268      *
269      * container.resolve!Animal; // Error: multiple candidates for type "Animal"
270      * container.resolve!Dog; // Error: No type is registered by concrete type "Dog", only by super type "Animal"
271      * ---
272      * You need to use the resolve method which allows you to specify a qualifier.
273      */
274     public RegistrationType resolve(RegistrationType)(
275         ResolveOption resolveOptions = ResolveOption.none)
276             if (!is(RegistrationType == struct)) {
277         return resolve!(RegistrationType, RegistrationType)(resolveOptions);
278     }
279 
280     /**
281      * Resolve dependencies using a qualifier.
282      *
283      * Dependencies can only resolved using this method if they are registered by super type.
284      *
285      * Resolved dependencies are automatically autowired before being returned.
286      *
287      * Returns:
288      * An instance is returned which is created according to the registration scope with which they are registered.
289      *
290      * Throws:
291      * ResolveException when type is not registered or there are multiple candidates available for type.
292      *
293      * Examples:
294      * Resolve dependencies registered by super type:
295      * ---
296      * class Cat : Animal { ... }
297      * class Dog : Animal { ... }
298      *
299      * container.register!(Animal, Cat);
300      * container.register!(Animal, Dog);
301      *
302      * container.resolve!(Animal, Cat);
303      * container.resolve!(Animal, Dog);
304      * ---
305      */
306     public QualifierType resolve(RegistrationType, QualifierType:
307         RegistrationType)(ResolveOption resolveOptions = ResolveOption.none)
308             if (!is(QualifierType == struct)) {
309         TypeInfo resolveType = typeid(RegistrationType);
310         TypeInfo qualifierType = typeid(QualifierType);
311 
312         debug (poodinisVerbose) {
313             writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString());
314         }
315 
316         auto candidates = resolveType in registrations;
317         if (!candidates) {
318             static if (is(typeof(typeid(QualifierType)) == TypeInfo_Class) && !__traits(isAbstractClass, QualifierType)) {
319                 if (hasOption(resolveOptions, persistentResolveOptions, ResolveOption
320                         .registerBeforeResolving)) {
321                     register!(RegistrationType, QualifierType)();
322                     return resolve!(RegistrationType, QualifierType)(resolveOptions);
323                 }
324             }
325 
326             if (hasOption(resolveOptions, persistentResolveOptions,
327                     ResolveOption.noResolveException)) {
328                 return null;
329             }
330 
331             throw new ResolveException("Type not registered.", resolveType);
332         }
333 
334         Registration registration = getQualifiedRegistration(resolveType,
335             qualifierType, cast(Registration[])*candidates);
336 
337         try {
338             QualifierType newInstance = resolveAutowiredInstance!QualifierType(registration);
339             callPostConstructors(newInstance);
340             return newInstance;
341         } catch (ValueInjectionException e) {
342             throw new ResolveException(e, resolveType);
343         }
344     }
345 
346     bool isRegistered(RegistrationType)() {
347         TypeInfo typeInfo = typeid(RegistrationType);
348         auto candidates = typeInfo in registrations;
349         return candidates !is null;
350     }
351 
352     private QualifierType resolveAutowiredInstance(QualifierType)(Registration registration) {
353         QualifierType instance;
354         if (!(cast(Registration[]) autowireStack).canFind(registration)) {
355             autowireStack ~= cast(shared(Registration)) registration;
356             instance = cast(QualifierType) registration.getInstance(
357                 new AutowireInstantiationContext());
358             autowireStack = autowireStack[0 .. $ - 1];
359         } else {
360             auto autowireContext = new AutowireInstantiationContext();
361             autowireContext.autowireInstance = false;
362             instance = cast(QualifierType) registration.getInstance(autowireContext);
363         }
364         return instance;
365     }
366 
367     /**
368      * Resolve all dependencies registered to a super type.
369      *
370      * Returns:
371      * An array of autowired instances is returned. The order is undetermined.
372      *
373      * Examples:
374      * ---
375      * class Cat : Animal { ... }
376      * class Dog : Animal { ... }
377      *
378      * container.register!(Animal, Cat);
379      * container.register!(Animal, Dog);
380      *
381      * Animal[] animals = container.resolveAll!Animal;
382      * ---
383      */
384     public RegistrationType[] resolveAll(RegistrationType)(
385         ResolveOption resolveOptions = ResolveOption.none) {
386         RegistrationType[] instances;
387         TypeInfo resolveType = typeid(RegistrationType);
388 
389         auto qualifiedRegistrations = resolveType in registrations;
390         if (!qualifiedRegistrations) {
391             if (hasOption(resolveOptions, persistentResolveOptions,
392                     ResolveOption.noResolveException)) {
393                 return [];
394             }
395 
396             throw new ResolveException("Type not registered.", resolveType);
397         }
398 
399         foreach (registration; cast(Registration[])*qualifiedRegistrations) {
400             instances ~= resolveAutowiredInstance!RegistrationType(registration);
401         }
402 
403         return instances;
404     }
405 
406     private Registration getQualifiedRegistration(TypeInfo resolveType,
407         TypeInfo qualifierType, Registration[] candidates) {
408         if (resolveType == qualifierType) {
409             if (candidates.length > 1) {
410                 string candidateList = candidates.toConcreteTypeListString();
411                 throw new ResolveException(
412                     "Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.",
413                     resolveType);
414             }
415 
416             return candidates[0];
417         }
418 
419         return getRegistration(candidates, qualifierType);
420     }
421 
422     private void callPostConstructors(Type)(Type instance) {
423         static foreach (memberName; __traits(allMembers, Type)) {
424             static  foreach (overload; __traits(getOverloads, Type, memberName)) {
425                 static if (__traits(compiles, __traits(getProtection, overload))
426                     && __traits(getProtection, overload) == "public"
427                     && isFunction!overload
428                     && hasUDA!(overload, PostConstruct)) {
429                     __traits(getMember, instance, memberName)();
430                 }
431             }
432         }
433     }
434 
435     /**
436      * Clears all dependency registrations managed by this container.
437      */
438     public void clearAllRegistrations() {
439         foreach (registrationsOfType; registrations) {
440             callPreDestructorsOfRegistrations(registrationsOfType);
441         }
442         registrations.destroy();
443     }
444 
445     /**
446      * Removes a registered dependency by type.
447      *
448      * A dependency can be removed either by super type or concrete type, depending on how they are registered.
449      *
450      * Examples:
451      * ---
452      * container.removeRegistration!Animal;
453      * ---
454      */
455     public void removeRegistration(RegistrationType)() {
456         auto registrationsOfType = *(typeid(RegistrationType) in registrations);
457         callPreDestructorsOfRegistrations(registrationsOfType);
458         registrations.remove(typeid(RegistrationType));
459     }
460 
461     private void callPreDestructorsOfRegistrations(shared(Registration[]) registrations) {
462         foreach (registration; registrations) {
463             Registration unsharedRegistration = cast(Registration) registration;
464             if (unsharedRegistration.preDestructor !is null) {
465                 unsharedRegistration.preDestructor()();
466             }
467         }
468     }
469 
470     /**
471      * Apply persistent registration options which will be used everytime register() is called.
472      */
473     public void setPersistentRegistrationOptions(RegistrationOption options) {
474         persistentRegistrationOptions = options;
475     }
476 
477     /**
478      * Unsets all applied persistent registration options
479      */
480     public void unsetPersistentRegistrationOptions() {
481         persistentRegistrationOptions = RegistrationOption.none;
482     }
483 
484     /**
485      * Apply persistent resolve options which will be used everytime resolve() is called.
486      */
487     public void setPersistentResolveOptions(ResolveOption options) {
488         persistentResolveOptions = options;
489     }
490 
491     /**
492      * Unsets all applied persistent resolve options
493      */
494     public void unsetPersistentResolveOptions() {
495         persistentResolveOptions = ResolveOption.none;
496     }
497 
498 }