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 }