1 /**
2  * Skadi.d Web Framework
3  *
4  * Forked from: https://github.com/mbierlee/poodinis
5  * Authors:  Mike Bierlee, Faianca
6  * Copyright: Copyright (c) 2015  Mike Bierlee, Faianca
7  * License: MIT License, see LICENSE
8  */
9 module skadi.container.inject;
10 
11 public import skadi.container.container;
12 
13 import std.exception;
14 import std.stdio;
15 import std.string;
16 import std.traits;
17 import std.range;
18 
19 struct UseMemberType {};
20 
21 /**
22  * UDA for annotating class members as candidates for Injecting.
23  *
24  * Optionally a template parameter can be supplied to specify the type of a qualified class. The qualified type
25  * of a concrete class is used to inject members declared by supertype. If no qualifier is supplied, the type
26  * of the member is used as qualifier.
27  *
28  * The members of an instance of "HybridCar" will now be injectd properly, because the inject mechanism will
29  * inject member "fuelEngine" as if it's of type "FuelEngine". This means that the members of instance "fuelEngine"
30  * will also be injectd because the inject mechanism knows that member "fuelEngine" is an instance of "FuelEngine"
31  */
32 struct Inject(QualifierType = UseMemberType)
33 {
34 	QualifierType qualifier;
35 };
36 
37 /**
38  * UDA for annotating class members to be injectd with a new instance regardless of their registration scope.
39  *
40  * antenna will always be assigned a new instance of class Antenna.
41  */
42 struct AssignNewInstance {}
43 
44 private void printDebugInjectedInstance(TypeInfo instanceType, void* instanceAddress)
45 {
46 	writeln(format("DEBUG: Injecting members of [%s@%s]", instanceType, instanceAddress));
47 }
48 
49 /**
50  * Injects members of a given instance using dependencies registered in the given container.
51  *
52  * All public members of the given instance, which are annotated using the "Inject" UDA, are injectd.
53  * All members are resolved using the given container. Qualifiers are used to determine the type of class to
54  * resolve for any member of instance.
55  *
56  * Note that private members will not be injectd because the autowiring mechanism is not able to by-pass
57  * member visibility protection.
58  *
59  * See_Also: Inject
60  */
61 public void inject(Type)(shared(Container) container, Type instance)
62 {
63 	debug(skadiVerbose) {
64 		printDebugInjectedInstance(typeid(Type), &instance);
65 	}
66 
67 	foreach (member ; __traits(allMembers, Type)) {
68 		injectMember!member(container, instance);
69 	}
70 }
71 
72 private void printDebugInjectingCandidate(TypeInfo candidateInstanceType, void* candidateInstanceAddress, TypeInfo instanceType, void* instanceAddress, string member)
73 {
74 	writeln(format("DEBUG: Injected instance [%s@%s] to [%s@%s].%s", candidateInstanceType, candidateInstanceAddress, instanceType, instanceAddress, member));
75 }
76 
77 private void printDebugInjectingArray(TypeInfo superTypeInfo, TypeInfo instanceType, void* instanceAddress, string member)
78 {
79 	writeln(format("DEBUG: Inject all registered instances of super type %s to [%s@%s].%s", superTypeInfo, instanceType, instanceAddress, member));
80 }
81 
82 private void injectMember(string member, Type)(shared(Container) container, Type instance)
83 {
84 	static if(__traits(compiles, __traits(getMember, instance, member)) && __traits(compiles, __traits(getAttributes, __traits(getMember, instance, member)))) {
85 		foreach(injectAttribute; __traits(getAttributes, __traits(getMember, instance, member))) {
86 			static if (__traits(isSame, injectAttribute, Inject) || is(injectAttribute == Inject!T, T)) {
87 				if (__traits(getMember, instance, member) is null) {
88 					alias MemberType = typeof(__traits(getMember, instance, member));
89 
90 					enum assignNewInstance = hasUDA!(__traits(getMember, instance, member), AssignNewInstance);
91 
92 					static if (isDynamicArray!MemberType) {
93 						alias MemberElementType = ElementType!MemberType;
94 						auto instances = container.resolveAll!MemberElementType;
95 						__traits(getMember, instance, member) = instances;
96 						debug(skadiVerbose) {
97 							printDebugInjectingArray(typeid(MemberElementType), typeid(Type), &instance, member);
98 						}
99 					} else {
100 						debug(skadiVerbose) {
101 							TypeInfo qualifiedInstanceType = typeid(MemberType);
102 						}
103 
104 						MemberType qualifiedInstance;
105 						static if (is(injectAttribute == Inject!T, T) && !is(injectAttribute.qualifier == UseMemberType)) {
106 							alias QualifierType = typeof(injectAttribute.qualifier);
107 							qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType, assignNewInstance)(container);
108 							debug(skadiVerbose) {
109 								qualifiedInstanceType = typeid(QualifierType);
110 							}
111 						} else {
112 							qualifiedInstance = createOrResolveInstance!(MemberType, MemberType, assignNewInstance)(container);
113 						}
114 
115 						__traits(getMember, instance, member) = qualifiedInstance;
116 
117 						debug(skadiVerbose) {
118 							printDebugInjectingCandidate(qualifiedInstanceType, &qualifiedInstance, typeid(Type), &instance, member);
119 						}
120 					}
121 				}
122 
123 				break;
124 			}
125 		}
126 	}
127 }
128 
129 private QualifierType createOrResolveInstance(MemberType, QualifierType, bool createNew)(shared(Container) container)
130 {
131 	static if (createNew) {
132 		auto instanceFactory = new NewInstanceScope(typeid(MemberType));
133 		return cast(MemberType) instanceFactory.getInstance();
134 	} else {
135 		return container.resolve!(MemberType, QualifierType);
136 	}
137 }
138 
139 /**
140  * Inject the given instance using the globally available dependency container.
141  *
142  * See_Also: Container
143  */
144 public void globalInject(Type)(Type instance)
145 {
146 	Container.getInstance().inject(instance);
147 }
148 
149 class InjectedRegistration(RegistrationType : Object) : Registration
150 {
151 	private shared(Container) container;
152 
153 	public this(TypeInfo registeredType, shared(Container) container)
154 	{
155 		enforce(!(container is null), "Argument 'container' is null. Injected registrations need to inject using a container.");
156 		this.container = container;
157 		super(registeredType, typeid(RegistrationType));
158 	}
159 
160 	public override Object getInstance(InstantiationContext context = new InjectInstantiationContext())
161 	{
162 		RegistrationType instance = cast(RegistrationType) super.getInstance(context);
163 
164 		InjectInstantiationContext injectContext = cast(InjectInstantiationContext) context;
165 		enforce(!(injectContext is null), "Given instantiation context type could not be cast to an InjectInstantiationContext. If you relied on using the default assigned context: make sure you're calling getInstance() on an instance of type InjectedRegistration!");
166 		if (injectContext.injectInstance) {
167 			container.inject(instance);
168 		}
169 
170 		return instance;
171 	}
172 
173 }
174 
175 class InjectInstantiationContext : InstantiationContext
176 {
177 	public bool injectInstance = true;
178 }