1 /** 2 Copyright: 2018 Mark Fisher 3 4 License: 5 Permission is hereby granted, free of charge, to any person obtaining a copy of 6 this software and associated documentation files (the "Software"), to deal in 7 the Software without restriction, including without limitation the rights to 8 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 of the Software, and to permit persons to whom the Software is furnished to do 10 so, subject to the following conditions: 11 12 The above copyright notice and this permission notice shall be included in all 13 copies or substantial portions of the Software. 14 15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 SOFTWARE. 22 **/ 23 /** 24 * A wrapper that creates injection tools for your project. 25 * The settings are passed as template parameters to the 26 * class LocalInjector which you instantiate using the 27 * static method newInjector. 28 * Only one instance of LocalInjector is created; 29 * subsequent invocations of newInjector will return 30 * the existing instance, and the parameters will be ignored. 31 * The parameters to newInjector are parsed: 32 * Class parameters are passed to the 33 * wrapped container using the "scan" method. 34 * Tuple parameters 35 * are parsed and converted into properties which 36 * are loaded at runtime from "dxx.json". 37 * TODO allow for the property filenames to be overridden. 38 **/ 39 module dxx.util.injector; 40 41 private import std.experimental.logger; 42 private import std.stdio; 43 private import std.process : environment; 44 private import std.typecons; 45 private import std.variant; 46 private import std..string : indexOf; 47 private import std.json; 48 49 private import aermicioi.aedi; 50 //private import aermicioi.aedi_property_reader; 51 52 private import dxx.constants; 53 private import dxx.util.ini; 54 private import dxx.util.config; 55 56 alias component = aermicioi.aedi.component; 57 alias autowired = aermicioi.aedi.autowired; 58 alias localInjector = InjectionContainer.getInstance; 59 60 static auto resolveInjector(alias T,Arg...)(Arg arg,InjectionContainer i=InjectionContainer.getInstance) { 61 return i.resolve!T(arg); 62 } 63 64 static T getInjectorProperty(T)(string k,InjectionContainer i=InjectionContainer.getInstance) { 65 return i.resolve!T(k); 66 } 67 68 static void setInjectorProperty(T)(string k,T t,InjectionContainer i=InjectionContainer.getInstance) { 69 i.register!T(t,k); 70 } 71 72 static auto newInjector(alias T,V...)(AggregateContainer c = null) { 73 synchronized(InjectionContainer.classinfo) { 74 if(InjectionContainer.getInstance is null) { 75 new LocalInjector!(T,V)(c); 76 } 77 } 78 return InjectionContainer.getInstance; 79 } 80 81 static void terminateInjector() { 82 synchronized(InjectionContainer.classinfo) { 83 if(InjectionContainer.getInstance) { 84 InjectionContainer.getInstance.terminate; 85 } 86 } 87 } 88 89 abstract class InjectionContainer { 90 91 private static __gshared InjectionContainer INSTANCE; 92 static bool instantiated = false; 93 94 static auto ref getInstance() { 95 //assert(INSTANCE !is null); 96 return INSTANCE; 97 } 98 99 @property 100 AggregateContainer _container; 101 102 void services(T)(T parent) { 103 auto s = singleton; 104 auto p = prototype; 105 106 configureSingleton(s); 107 scanPrototype(p); 108 109 parent.set(s,"singleton"); 110 parent.set(p,"prototype"); 111 112 } 113 abstract void scanPrototype(PrototypeContainer); 114 abstract void configureSingleton(SingletonContainer); 115 116 this(AggregateContainer _c) { 117 synchronized(InjectionContainer.classinfo) { 118 if(!instantiated) { 119 if(INSTANCE is null) { 120 services(_c); 121 _container = _c; 122 INSTANCE = this; 123 } 124 instantiated = true; 125 } 126 } 127 } 128 129 auto resolve(T,Arg ...)(Arg arg) { 130 return _container.locate!T(arg); 131 } 132 133 void register(T...)(const(string) arg) { 134 _container.configure("prototype").register!T(arg); 135 } 136 void register(T...)() { 137 _container.configure("prototype").register!T(); 138 } 139 void register(T)(ref T t,const(string) arg) { 140 _container.configure("singleton").register!T(t,arg); 141 } 142 143 void terminate() { 144 synchronized(InjectionContainer.classinfo) { 145 if(instantiated) { 146 debug(Injector) { 147 sharedLog.info("terminating"); 148 } 149 _container.terminate(); 150 } 151 if(INSTANCE is this) { 152 INSTANCE = null; 153 instantiated = false; 154 } 155 } 156 } 157 auto instantiate() { 158 debug(Injector) { 159 sharedLog.info("instantiating"); 160 } 161 return _container.instantiate; 162 } 163 //abstract void load(T : DocumentContainer!X, X...)(T container); 164 } 165 166 167 final class LocalInjector(C...) : InjectionContainer { 168 this(AggregateContainer c = null) { 169 if(c is null) c = aggregate(config, "parameters"); 170 super(c); 171 } 172 override void scanPrototype(PrototypeContainer p) { 173 static foreach(c;C) { 174 static if(isTuple!c is false) { 175 debug(Injector) { 176 pragma(msg,"scanning prototype: "); 177 pragma(msg,c); 178 sharedLog.info("Scanning prototype ",typeid(c)); 179 } 180 p.scan!c; 181 } 182 } 183 } 184 override void configureSingleton(SingletonContainer) { 185 } 186 187 auto config() { 188 debug(Injector) { 189 sharedLog.info("Injector config"); 190 } 191 /*version(DXX_Developer) { 192 auto cont = container( 193 argument, 194 env, 195 //json("./dxx-dev.json"), 196 json("./resources/dxx-dev.json"), 197 json(RTConstants.constants.appDir ~ "/../../resources/dxx-dev.json"), 198 //json("./dxx.json"), 199 //json("./resource/dxx.json"), 200 //json(RTConstants.constants.appDir ~ "/../dxx.json") 201 ); 202 } else { 203 auto cont = container( 204 argument, 205 env, 206 json("./resources/dxx.json"), 207 json(RTConstants.constants.appDir ~ "/../resources/dxx.json") 208 //json("/etc/aedi-example/config.json"), 209 //configFiles 210 ); 211 212 }*/ 213 /*auto cont = container( 214 prototype 215 ); 216 auto __j = loadJson("resources/dxx.json"); 217 218 foreach (c; cont) { 219 load(c,__j); 220 } 221 return cont;*/ 222 auto cont = values; 223 version(DXX_Developer) { 224 //auto __j = loadJson("resources/dxx-dev.json"); 225 load(cont,loadJson("resources/dxx-dev.json")); 226 } else version(Unittest) { 227 //auto __j = loadJson("resources/dxx-ut.json"); 228 load(cont,loadJson("resources/dxx-ut.json")); 229 } else { 230 //auto __j = loadJson("resources/dxx.json"); 231 load(cont,loadJson("resources/dxx.json")); 232 load(cont,loadJson("dxx.json")); 233 } 234 // parse the environment 235 // parse the command line 236 load(cont,cast(string[])runtimeConstants.argsApp.dup); 237 return cont; 238 } 239 auto loadJson(string pathOrData) { 240 import std.file : exists, readText; 241 if (pathOrData.exists) { 242 debug(trace) trace("Loading json from ", pathOrData); 243 pathOrData = pathOrData.readText(); 244 } 245 try { 246 return parseJSON(pathOrData); 247 } catch(Exception e) { 248 return parseJSON("{}"); 249 } 250 } 251 string[] toStringArray(const(JSONValue)[] ar) { 252 string[] res; 253 foreach(v;ar) { 254 debug(Injector) { 255 sharedLog.trace("array ",v); 256 } 257 res ~= v.str; 258 } 259 return res; 260 } 261 Variant readValue(const(JSONValue) j,string name) { 262 auto v = LocalConfig.get(name); 263 if(v != null) return v; 264 //return null; 265 // scan env 266 // scan args 267 // scan properties 268 auto inx = name.indexOf('.'); 269 if(inx != -1) { 270 string n = name[0..inx]; 271 if(const(JSONValue)* x = n in j) { 272 return readValue(*x,name[inx+1..$]); 273 } 274 } 275 if(const (JSONValue)* val = name in j) { 276 debug(Injector) { 277 sharedLog.trace(name," = ",*val); 278 } 279 /*static if(is(_T == int)) { 280 return Variant(val.integer); 281 } else if(is(_T == uint)) { 282 return Variant(val.integer); 283 } else if(is(_T == bool)) { 284 return Variant(val.boolean); 285 } else if (is(_T == immutable(char)[])) { 286 return Variant(val.str); 287 } else if (is(_T == string[])) { 288 string[] vals; 289 vals = toStringArray(val.array); 290 return Variant(vals); 291 }*/ 292 switch(val.type) { 293 case(JSONType.integer): 294 return Variant(val.integer); 295 case(JSONType.true_): 296 case(JSONType.false_): 297 return Variant(val.boolean); 298 case(JSONType..string): 299 return Variant(val.str); 300 case(JSONType.array): 301 string[] vals; 302 vals = toStringArray(val.array); 303 return Variant(vals); 304 default: 305 return Variant(null); 306 } 307 308 } 309 /*} else { 310 static if(is(_T == int)) { 311 return Variant(-1); 312 } else if(is(_T == bool)) { 313 return Variant(false); 314 } else if (is(_T == string)) { 315 return Variant(""); 316 } else if (is(_T == string[])) { 317 string[] x = []; 318 return Variant(x); 319 } 320 }*/ 321 return Variant(null); 322 } 323 //void load(T : DocumentContainer!X, X...)(T container) { 324 void load(T)(T container,const(JSONValue) __j) { 325 load(container,x=>readValue(__j,x)); 326 } 327 void load(T)(T container,string[] cmd) { 328 //load(container,x=>readValue(__j,x)); 329 } 330 void load(T)(T container,Variant delegate (string) getVal) { 331 with (container.configure) { 332 static foreach(c;C) { 333 static if(isTuple!c) { 334 template _reg(alias n,alias T) { 335 void _reg() { 336 static foreach (name ; T.fieldNames) { 337 { 338 enum fieldName = n ~ name; 339 mixin("alias _f = c." ~ fieldName~";"); 340 alias fieldType = typeof(_f); 341 debug(Injector) { 342 import std.conv; 343 sharedLog.trace("field: " ~ typeid(fieldType).to!string ~ " " ~ fieldName); 344 } 345 static if(isTuple!fieldType) { 346 _reg!(fieldName ~ ".",fieldType)(); 347 } else { 348 //auto v = readValue(__j,fieldName); 349 auto v = getVal(fieldName); 350 if(v.peek!fieldType !is null) { 351 debug(Injector) { 352 sharedLog.trace(fieldName," = ",v.get!fieldType); 353 } 354 register!fieldType(v.get!fieldType,fieldName); 355 } 356 } 357 } 358 } 359 } 360 } 361 _reg!("",c)(); 362 } 363 // Scan properties from the .ini file. 364 // Make them all strings. 365 iterateValuesF!(DXXConfig.keys)( (string fqn,string k,string v) { 366 register!string(k); 367 } ); 368 } 369 } 370 } 371 } 372 373 unittest { 374 class MyClass { 375 } 376 377 @component 378 class MyModule { 379 @component 380 public MyClass getMyClass() { 381 return new MyClass; 382 } 383 } 384 debug { 385 sharedLog.info("Starting component unittest."); 386 } 387 terminateInjector; 388 auto injector = newInjector!MyModule; 389 assert(injector !is null); 390 auto my = injector.resolve!MyClass; 391 assert(my !is null); 392 } 393 394 unittest { 395 alias param = Tuple!( 396 string,"name", 397 long,"age", 398 string[string],"properties" 399 ); 400 debug { 401 sharedLog.info("Starting injector tuple parameters unittest."); 402 } 403 terminateInjector; 404 auto injector = newInjector!param; 405 assert(injector !is null); 406 auto name = injector.resolve!string("name"); 407 auto age = injector.resolve!long("age"); 408 } 409 410 unittest { 411 struct Param { 412 string name; 413 long age; 414 string[string] properties; 415 } 416 alias param = Tuple!(Param,"param"); 417 debug { 418 sharedLog.info("Starting injector struct parameters unittest."); 419 } 420 terminateInjector; 421 auto injector = newInjector!param; 422 assert(injector !is null); 423 auto name = injector.resolve!string("param.name"); 424 auto age = injector.resolve!long("param.age"); 425 }