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 }