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.container;
10 
11 import std.string;
12 import std.algorithm;
13 import std.concurrency;
14 
15 debug {
16 	import std.stdio;
17 }
18 
19 public import skadi.container.registration;
20 public import skadi.container.inject;
21 
22 /**
23  * Exception thrown when errors occur while resolving a type in a dependency container.
24  */
25 class ResolveException : Exception
26 {
27 	this(string message, TypeInfo resolveType)
28 	{
29 		super(format("Exception while resolving type %s: %s", resolveType.toString(), message));
30 	}
31 }
32 
33 /**
34  * Exception thrown when errors occur while registering a type in a dependency container.
35  */
36 class RegistrationException : Exception
37 {
38 	this(string message, TypeInfo registrationType)
39 	{
40 		super(format("Exception while registering type %s: %s", registrationType.toString(), message));
41 	}
42 }
43 
44 /**
45  * Options which influence the process of registering dependencies
46  */
47 public enum RegistrationOptions
48 {
49 	/**
50 	 * When registering a type by its supertype, providing this option will also register
51 	 * a linked registration to the type itself.
52 	 *
53 	 * This allows you to resolve that type both by super type and concrete type using the
54 	 * same registration scope (and instance managed by this scope).
55 	 */
56 	ADD_CONCRETE_TYPE_REGISTRATION
57 }
58 
59 /**
60  * The dependency container maintains all dependencies registered with it.
61  *
62  * Dependencies registered by a container can be resolved as long as they are still registered with the container.
63  * Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of
64  * dependencies are created. Resolved dependencies will be injected before being returned.
65  *
66  * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies.
67  * You can still create new instances of this class for exceptional situations.
68  */
69 synchronized class Container
70 {
71 	private Registration[][TypeInfo] registrations;
72 
73 	private Registration[] injectStack;
74 
75 	/**
76 	 * Register a dependency by concrete class type.
77 	 *
78 	 * A dependency registered by concrete class type can only be resolved by concrete class type.
79 	 * No qualifiers can be used when resolving dependencies which are registered by concrete type.
80 	 *
81 	 * The default registration scope is "single instance" scope.
82 	 *
83 	 * Returns:
84 	 * A registration is returned which can be used to change the registration scope.
85 	 */
86 	public Registration register(ConcreteType)()
87 	{
88 		return register!(ConcreteType, ConcreteType)();
89 	}
90 
91 	/**
92 	 * Register a dependency by super type.
93 	 *
94 	 * A dependency registered by super type can only be resolved by super type. A qualifier is typically
95 	 * used to resolve dependencies registered by super type.
96 	 *
97 	 * The default registration scope is "single instance" scope.
98 	 *
99 	 * See_Also: singleInstance, newInstance, existingInstance, RegistrationOptions
100 	 */
101 	public Registration register(SuperType, ConcreteType : SuperType, RegistrationOptionsTuple...)(RegistrationOptionsTuple options)
102 	{
103 		TypeInfo registeredType = typeid(SuperType);
104 		TypeInfo_Class concreteType = typeid(ConcreteType);
105 
106 		debug(skadiVerbose) {
107 			writeln(format("DEBUG: Register type %s (as %s)", concreteType.toString(), registeredType.toString()));
108 		}
109 
110 		auto existingRegistration = getExistingRegistration(registeredType, concreteType);
111 		if (existingRegistration) {
112 			return existingRegistration;
113 		}
114 
115 		auto newRegistration = new InjectedRegistration!ConcreteType(registeredType, this);
116 		newRegistration.singleInstance();
117 
118 		if (hasOption(options, RegistrationOptions.ADD_CONCRETE_TYPE_REGISTRATION)) {
119 			static if (!is(SuperType == ConcreteType)) {
120 				auto concreteTypeRegistration = register!ConcreteType;
121 				concreteTypeRegistration.linkTo(newRegistration);
122 			} else {
123 				throw new RegistrationException("Option ADD_CONCRETE_TYPE_REGISTRATION cannot be used when registering a concrete type registration", concreteType);
124 			}
125 		}
126 
127 		registrations[registeredType] ~= cast(shared(Registration)) newRegistration;
128 		return newRegistration;
129 	}
130 
131 	private bool hasOption(RegistrationOptionsTuple...)(RegistrationOptionsTuple options, RegistrationOptions option)
132 	{
133 		foreach(presentOption ; options) {
134 			if (presentOption == option) {
135 				return true;
136 			}
137 		}
138 
139 		return false;
140 	}
141 
142 	private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType)
143 	{
144 		auto existingCandidates = registrationType in registrations;
145 		if (existingCandidates) {
146 			return getRegistration(cast(Registration[]) *existingCandidates, qualifierType);
147 		}
148 
149 		return null;
150 	}
151 
152 	private Registration getRegistration(Registration[] candidates, TypeInfo concreteType)
153 	{
154 		foreach(existingRegistration ; candidates) {
155 			if (existingRegistration.instantiatableType == concreteType) {
156 				return existingRegistration;
157 			}
158 		}
159 
160 		return null;
161 	}
162 
163 	/**
164 	 * Resolve dependencies.
165 	 *
166 	 * Dependencies can only resolved using this method if they are registered by concrete type or the only
167 	 * concrete type registered by super type.
168 	 *
169 	 * Resolved dependencies are automatically injected before being returned.
170 	 *
171 	 * Returns:
172 	 * An instance is returned which is created according to the registration scope with which they are registered.
173 	 *
174 	 * Throws:
175 	 * ResolveException when type is not registered.
176 
177 	 * You need to use the resolve method which allows you to specify a qualifier.
178 	 */
179 	public RegistrationType resolve(RegistrationType)()
180 	{
181 		return resolve!(RegistrationType, RegistrationType)();
182 	}
183 
184 	/**
185 	 * Resolve dependencies using a qualifier.
186 	 *
187 	 * Dependencies can only resolved using this method if they are registered by super type.
188 	 *
189 	 * Resolved dependencies are automatically injected before being returned.
190 	 *
191 	 * Returns:
192 	 * An instance is returned which is created according to the registration scope with which they are registered.
193 	 *
194 	 * Throws:
195 	 * ResolveException when type is not registered or there are multiple candidates available for type.
196 	 */
197 	public QualifierType resolve(RegistrationType, QualifierType : RegistrationType)()
198 	{
199 		TypeInfo resolveType = typeid(RegistrationType);
200 		TypeInfo qualifierType = typeid(QualifierType);
201 
202 		debug(skadiVerbose) {
203 			writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString());
204 		}
205 
206 		auto candidates = resolveType in registrations;
207 		if (!candidates) {
208 			throw new ResolveException("Type not registered.", resolveType);
209 		}
210 
211 		Registration registration = getQualifiedRegistration(resolveType, qualifierType, cast(Registration[]) *candidates);
212 		return resolveInjectedInstance!QualifierType(registration);
213 	}
214 
215 	private QualifierType resolveInjectedInstance(QualifierType)(Registration registration)
216 	{
217 		QualifierType instance;
218 		if (!(cast(Registration[]) injectStack).canFind(registration)) {
219 			injectStack ~= cast(shared(Registration)) registration;
220 			instance = cast(QualifierType) registration.getInstance(new InjectInstantiationContext());
221 			injectStack = injectStack[0 .. $-1];
222 		} else {
223 			auto injectContext = new InjectInstantiationContext();
224 			injectContext.injectInstance = false;
225 			instance = cast(QualifierType) registration.getInstance(injectContext);
226 		}
227 		return instance;
228 	}
229 
230 	/**
231 	 * Resolve all dependencies registered to a super type.
232 	 *
233 	 * Returns:
234 	 * An array of injected instances is returned. The order is undetermined.
235 	 */
236 	public RegistrationType[] resolveAll(RegistrationType)()
237 	{
238 		RegistrationType[] instances;
239 		TypeInfo resolveType = typeid(RegistrationType);
240 
241 		auto qualifiedRegistrations = resolveType in registrations;
242 		if (!qualifiedRegistrations) {
243 			throw new ResolveException("Type not registered.", resolveType);
244 		}
245 
246 		foreach(registration ; cast(Registration[]) *qualifiedRegistrations) {
247 			instances ~= resolveInjectedInstance!RegistrationType(registration);
248 		}
249 
250 		return instances;
251 	}
252 
253 	private Registration getQualifiedRegistration(TypeInfo resolveType, TypeInfo qualifierType, Registration[] candidates)
254 	{
255 		if (resolveType == qualifierType) {
256 			if (candidates.length > 1) {
257 				string candidateList = candidates.toConcreteTypeListString();
258 				throw new ResolveException("Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.", resolveType);
259 			}
260 
261 			return candidates[0];
262 		}
263 
264 		return getRegistration(candidates, qualifierType);
265 	}
266 
267 	/**
268 	 * Clears all dependency registrations managed by this container.
269 	 */
270 	public void clearAllRegistrations()
271 	{
272 		registrations.destroy();
273 	}
274 
275 	/**
276 	 * Removes a registered dependency by type.
277 	 *
278 	 * A dependency can be removed either by super type or concrete type, depending on how they are registered.
279 	 *
280 	 */
281 	public void removeRegistration(RegistrationType)()
282 	{
283 		registrations.remove(typeid(RegistrationType));
284 	}
285 
286 	/**
287 	 * Returns a global singleton instance of a dependency container.
288 	 */
289 	public static shared(Container) getInstance()
290 	{
291 		static shared Container instance;
292 		if (instance is null) {
293 			instance = new Container();
294 		}
295 		return instance;
296 	}
297 
298 }