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 }