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 }