1 module glued.application.runtime;
2 
3 import std.range;
4 import std.functional;
5 
6 import glued.logging;
7 
8 import glued.codescan;
9 
10 import glued.application.scanlisteners;
11 import glued.application.logging;
12 import glued.adhesives;
13 
14 import dejector;
15 
16 interface ApplicationAction {
17     void execute();
18 }
19 
20 interface ShutdownHandler {
21     void onShutdown();
22 }
23 
24 enum RuntimeLifecycleStage {
25     PREPARED,
26     STARTED,
27     SHUT_DOWN
28 }
29 
30 class GluedRuntime(alias scannables) {
31     mixin CreateLogger;
32     private Logger log;
33 
34     private RuntimeLifecycleStage _stage = RuntimeLifecycleStage.PREPARED;
35     private Dejector _injector;
36     private LogSink _targetSink;
37     
38     @property
39     RuntimeLifecycleStage currentStage(){
40         return _stage;
41     }
42     
43     @property
44     auto injector(){
45         return _injector; //todo optional?
46     }
47     
48     @property
49     auto targetSink(){
50         return _targetSink;
51     }
52     
53     //todo expose effective-/configuredSink? the deferred one, or only the filtering one?
54     
55     @property
56     void targetSink(LogSink sink){
57         assert(_stage == RuntimeLifecycleStage.PREPARED); //todo exception
58         _targetSink = sink;
59     }
60     
61     void start(string[] cmdLineArgs=[]){
62         assert(_stage == RuntimeLifecycleStage.PREPARED); //todo exception
63         DeferredLogSink sink = new DeferredLogSink;
64         log = Logger(sink);
65         log.debug_.emit("Initialized deferred log sink");
66         log.debug_.emit("Setting up DI facilities");
67         _injector = new Dejector;
68         _injector.bind!(LogSink)(new InstanceProvider(sink));
69         auto scanner = new CodebaseScanner!(Dejector, GluedAppListeners)(_injector, sink);
70         log.debug_.emit("Scanning: ", scannables);
71         scanner.scan!scannables();
72         log.debug_.emit("Scan finished, freezing application state");
73         scanner.freeze(); //todo this will happen in different moment when we compose scannables from annotations
74         log.debug_.emit("Resolving log sink based on loaded configuration");
75         resolveLogSink(sink); //todo if there is exception before this step, turn off any filtering (maybe keep buildLog.conf), flush to stderr, then let the failure propagate (so we can investigate, but with logs)
76         //        instantiateComponents(); //todo do this once you index types by stereotypes
77 //interface StereotypedInstance(S) {prop S stereotype, prop Object instance}
78 //interface StereotypeDescriptor{ prop string stereotypeTypeName }
79         log.debug_.emit("Running application actions");
80         runActions();
81         log.info.emit("Runtime started");
82         _stage = RuntimeLifecycleStage.STARTED; //todo on exception -> SHUT_DOWN
83     }
84     
85     //todo test this by providing some small app with bunch of components; provide glued assets for their log levels, steer some with build time assets too; set targetSink manually, provide action that triggers these components methods (which do logging) and make sure that related events are prezent
86     private void resolveLogSink(DeferredLogSink sink){
87         auto logFilteringTree = _injector
88             .get!Config
89             .view
90             .subtree("log.level")
91             .mapValues!Level(toDelegate((ConfigEntry v) => v.text.toLevel));
92         if (_targetSink is null)
93             _targetSink = new StdoutSink; //todo build sink based on config (stdout/err, some storage, maybe composites?); what to do if _targetSink !is null?
94         //todo levelConfig can also be tweaked via config
95         auto filteringSink = new FilteringSink(_targetSink, levelConfig(logFilteringTree));
96         sink.resolve(filteringSink);
97     }
98     
99     private void runActions(){
100         InterfaceResolver resolver = _injector.get!InterfaceResolver;
101         auto actions = resolver.getImplementations!(ApplicationAction)();
102         if (!actions.empty) {
103             log.debug_.emit("Found ", actions.length, " actions to execute");
104             foreach(a; actions){ //todo make optionally parallel via glued.app.actions.parallel
105                 a.execute();
106             }
107         } else {
108             log.debug_.emit("No actions found");
109         }
110     }
111     
112     void shutDown(){
113         assert(_stage == RuntimeLifecycleStage.STARTED); //todo exception
114         log.debug_.emit("Shutdown started");
115         InterfaceResolver resolver = _injector.get!InterfaceResolver;
116         
117         ShutdownHandler[] handlers = resolver.getImplementations!ShutdownHandler;
118         if (!handlers.empty) {
119             log.debug_.emit("Found ", handlers.length, " shutdown handlers");
120             foreach(h; handlers){ //todo ditto
121                 h.onShutdown();
122             }
123         } else {
124             log.debug_.emit("No shutdown handlers found");
125         }
126         _injector = null;
127         log.info.emit("Runtime shut down");
128         _stage = RuntimeLifecycleStage.SHUT_DOWN;
129     }
130 }