1 /**
2 Copyright 2018 Mark Fisher
3 
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
5 this software and associated documentation files (the "Software"), to deal in
6 the Software without restriction, including without limitation the rights to
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 of the Software, and to permit persons to whom the Software is furnished to do
9 so, subject to the following conditions:
10 
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13 
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21 **/
22 module dxx.util.injector;
23 
24 //private import std.algorithm : each;
25 //private import std.variant;
26 private import std.experimental.logger;
27 private import std.stdio;
28 private import std.process : environment;
29 private import std.typecons;
30 
31 private import aermicioi.aedi;
32 private import aermicioi.aedi_property_reader;
33 
34 private import dxx.sys.constants;
35 private import dxx.util.ini;
36 //private import dxx.util.storage;
37 private import dxx.util.config;
38 
39 
40 alias component = aermicioi.aedi.component;
41 
42 //static Variant[string] readInjectorProperties(File* f) {
43 //    Variant[string] res;
44 //    return res;
45 //}
46 //
47 //static void registerInjectorProperties(Variant[string] properties) {
48     //InjectionContainer._DEFAULT_CONTAINER.each!(c=>{
49     //    with(c.configure) {
50     //        properties.each!((k,v)=>{
51     //            if(v.type == typeid(string)) {
52     //            }
53     //        });
54     //    }
55     //});
56     //properties.keys.each!((k)=>{
57 //	    if(v.type == typeid(string)) {
58 	//    	InjectionContainer.register!string(k);
59 //        }
60 //    });
61 //}
62 
63 static auto resolveInjector(alias T,Arg...)(Arg arg,InjectionContainer i=InjectionContainer.getInstance) {
64     return i.resolve!T(arg);
65 }
66 
67 static T getInjectorProperty(T)(string k,InjectionContainer i=InjectionContainer.getInstance) {
68     return i.resolve!T(k);
69 }
70 
71 static void setInjectorProperty(T)(string k,T t,InjectionContainer i=InjectionContainer.getInstance) {
72     i.register!T(t,k);
73 }
74 
75 static auto newInjector(alias T,V...)(AggregateContainer c = null) {
76     if(InjectionContainer.INSTANCE is null) {
77         new ContextInjector!(T,V)(c);
78     }
79     return InjectionContainer.getInstance;
80 }
81 
82 static void terminateInjector() {
83     synchronized(InjectionContainer.classinfo) {
84         if(InjectionContainer.INSTANCE) {
85             InjectionContainer.INSTANCE.terminate;
86         }
87     }
88 }
89 
90 //interface InjectionComponent {
91 //    void registerComponent(InjectionContainer injector);
92 //
93 //}
94 
95 abstract class InjectionContainer {
96 
97     private static __gshared InjectionContainer INSTANCE;
98     static bool instantiated = false;
99 
100     static auto ref getInstance() {
101         //assert(INSTANCE !is null);
102         return INSTANCE;
103     }
104 
105     @property
106     AggregateContainer _container;
107 
108     void services(T)(T parent) {
109         auto s = singleton;
110         auto p = prototype;
111 
112         configureSingleton(s);
113         scanPrototype(p);
114 
115         parent.set(s,"singleton");
116         parent.set(p,"prototype");
117 
118     }
119     abstract void scanPrototype(PrototypeContainer);
120     abstract void configureSingleton(SingletonContainer);
121     //abstract void configureGlobals(AggregateContainer _container);
122 
123     this(AggregateContainer _c) {
124         synchronized(InjectionContainer.classinfo) {
125             if(!instantiated) {
126                 if(INSTANCE is null) {
127                     services(_c);
128                     _container = _c;
129                     INSTANCE = this;
130                 }
131                 instantiated = true;
132             }
133         }
134     }
135 
136     auto resolve(T,Arg ...)(Arg arg) {
137         return _container.locate!T(arg);
138     }
139 
140     void register(T...)(const(string) arg) {
141         with(_container.configure("prototype")) {
142             register!T(arg);
143         }
144     }
145     void register(T...)() {
146         with(_container.configure("prototype")) {
147             register!T();
148         }
149     }
150     void register(T)(ref T t,const(string) arg) {
151         with(_container.configure("singleton")) {
152             register!T(t,arg);
153         }
154     }
155     void setParam(T)(string k,T v) {
156         with(container.configure("parameters")) {
157             register!T(v,k);
158         }
159     }
160     T getParam(T)(string k) {
161         return _container.locate!T(k);
162     }
163     void terminate() {
164         debug(Injector) {
165             sharedLog.info("terminating");
166         }
167         _container.terminate();
168     }
169     auto instantiate() {
170         debug(Injector) {
171             sharedLog.info("instantiating");
172         }
173         return _container.instantiate;
174     }
175     abstract void load(T : DocumentContainer!X, X...)(T container);
176 }
177 
178 
179 final class ContextInjector(C...) : InjectionContainer {
180     this(AggregateContainer c = null) {
181         if(c is null) c = aggregate(config, "parameters");
182         super(c);
183     }
184     override void scanPrototype(PrototypeContainer p) {
185         debug(Injector) {
186             sharedLog.info("scanPrototype");
187         }
188         static foreach(c;C) {
189             debug(Injector) {
190                 import std.conv;
191                 sharedLog.info("Scanning prototype: " ~ typeid(c).to!string);
192             }
193             static if(isTuple!c) {
194             } else {
195                 debug(Injector) {
196                     pragma(msg,"scanning prototype: ");
197                     pragma(msg,c);
198                 }
199                 p.scan!c;
200             }
201         }
202     }
203     override void configureSingleton(SingletonContainer) {
204     }
205 
206     auto config() {
207         debug(Injector) {
208             sharedLog.info("Injector config");
209         }
210         auto cont = container(
211           argument,
212           env,
213           json("./dxx.json"),
214           json(RTConstants.constants.appDir ~ "/dxx.json")
215           //json("/etc/aedi-example/config.json"),
216           //configFiles
217            );
218         foreach (c; cont) {
219             load(c);
220         }
221         return cont;
222     }
223     void load(T : DocumentContainer!X, X...)(T container) {
224         with (container.configure) {
225             static foreach(c;C) {
226                 static if(isTuple!c) {
227                     debug(Injector) {
228                         pragma(msg,"scanning parameters: ");
229                         pragma(msg,c);
230                     }
231                     register!string;
232                     //register!uint;
233                     //register!int;
234                     register!long;
235 
236                     static foreach (fieldName ; c.fieldNames) {
237                         {
238                             mixin("alias f = c." ~ fieldName~";");
239                             alias fieldType = typeof(f);
240                             debug(Injector) {
241                                 import std.conv;
242                                 sharedLog.info("field: " ~ typeid(fieldType).to!string ~ " " ~ fieldName);
243                             }
244                             register!fieldType(fieldName);
245                         }
246                     }
247                 }
248                 // Scan properties from the .ini file.
249                 // Make them all strings.
250                 iterateValuesF!(DXXConfig.keys)( (string fqn,string k,string v) {
251                     //properties[k]=v;
252                     register!string(k);
253                 } );
254             }
255         }
256     }
257 }
258 
259 unittest {
260     class MyClass {
261     }
262 
263     @component
264     class MyModule {
265         @component
266         public MyClass getMyClass() {
267             return new MyClass;
268         }
269     }
270     debug {
271         sharedLog.info("Starting component unittest.");
272     }
273     auto injector = newInjector!MyModule;
274     assert(injector !is null);
275     auto my = injector.resolve!MyClass;
276     assert(my !is null);
277 }
278 
279 unittest {
280     alias param = Tuple!(
281         string,"name",
282         long,"age"
283     );
284     debug {
285         sharedLog.info("Starting injector parameters unittest.");
286     }
287     auto injector = newInjector!param;
288     assert(injector !is null);
289     auto name = injector.resolve!string("name");
290     auto age = injector.resolve!long("age");
291 }