1 /**
2  * The module implements application skeleton
3  *
4  * Copyright: (c) 2015-2017, Milofon Project.
5  * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file.
6  * Authors: Maksim Galanin
7  */
8 module dango.system.application;
9 
10 public
11 {
12     import BrightProof : SemVer;
13     import proped : Properties, Loader;
14     import proped.loader : createPropertiesLoader;
15 
16     import poodinis : DependencyContainer, existingInstance;
17 
18     import vibe.core.log : registerLogger, logInfo, logDiagnostic;
19     import vibe.core.core : runEventLoop, lowerPrivileges, runTask, Task;
20 
21     import dango.system.commandline : CommandLineProcessor;
22     import dango.system.properties : PropertiesProxy;
23 }
24 
25 private
26 {
27     import std.array : empty;
28 
29     import dango.system.properties : createLoaderFromContainer, PropertiesContext;
30     import dango.system.logging : configureLogging, LoggingContext;
31 }
32 
33 
34 /**
35  * Интерфейс приложения
36  */
37 interface Application
38 {
39     /**
40      * Запуск приложения
41      *
42      * Params:
43      * args = Входящие параметры
44      *
45      * Returns: Код завершения работы приложения
46      */
47     int run(string[] args);
48 
49     /**
50      * Функция загружает свойства из файла при помощи локального загрузчика
51      * Params:
52      *
53      * filePath = Путь до файла
54      *
55      * Returns: Объект свойств
56      */
57     Properties loadProperties(string filePath);
58 
59     /**
60      * Свойство возвращает наименование приложения
61      */
62     string name() @property pure nothrow;
63 
64     /**
65      * Свойство возвращает версию приложения
66      */
67     SemVer release() @property pure nothrow;
68 }
69 
70 
71 /**
72  * Базовый класс приложения
73  */
74 abstract class BaseApplication : Application
75 {
76     private
77     {
78         string _applicationName;
79         SemVer _applicationVersion;
80         shared(DependencyContainer) _container;
81         string[] _configFiles;
82         Loader _propLoader;
83     }
84 
85 
86     this(string name, string _version)
87     {
88         this(name, SemVer(_version));
89     }
90 
91 
92     this(string name, SemVer _version)
93     {
94         _applicationName = name;
95         _applicationVersion = _version;
96     }
97 
98     /**
99      * See_Also: Application.run
100      */
101     final int run(string[] args)
102     {
103         // иницмализируем зависимости
104         _container = new shared(DependencyContainer)();
105         _propLoader = createPropertiesLoader();
106 
107         // загружаем параметры командной строки
108         auto cProcessor = new CommandLineProcessor(args);
109         if (!doParseCommandLine(cProcessor))
110             return 1;
111 
112         if (_configFiles.empty)
113             _configFiles = getDefaultConfigFiles();
114 
115         Properties config;
116 
117         foreach(string cFile; _configFiles)
118             config ~= loadProperties(cFile);
119 
120         config ~= cProcessor.getOptionProperties();
121         config ~= cProcessor.getEnvironmentProperties();
122 
123         doInitDependencies(_container, config);
124 
125         configureLogging(container, config, &registerLogger);
126 
127         initDependencies(container, config);
128 
129         return runApplication(config);
130     }
131 
132     /**
133      * Свойство возвращает локальный контейнер
134      */
135     shared(DependencyContainer) container() @property pure nothrow
136     {
137         return _container;
138     }
139 
140     /**
141      * Свойство возвращает наименование приложения
142      */
143     string name() @property pure nothrow
144     {
145         return _applicationName;
146     }
147 
148     /**
149      * Свойство возвращает версию приложения
150      */
151     SemVer release() @property pure nothrow
152     {
153         return _applicationVersion;
154     }
155 
156     /**
157      * Функция загружает свойства из файла при помощи локального загрузчика
158      */
159     Properties loadProperties(string filePath)
160     {
161         if (_propLoader is null)
162             _propLoader = createPropertiesLoader();
163         return _propLoader(filePath);
164     }
165 
166 protected:
167 
168     /**
169      * Запуск приложения
170      *
171      * Params:
172      * config = Входящие параметры
173      *
174      * Returns: Код завершения работы приложения
175      */
176     int runApplication(Properties config);
177 
178     /**
179      * Возвращает пути до файлов по-умолчанию
180      */
181     string[] getDefaultConfigFiles()
182     {
183         return [];
184     }
185 
186     /**
187      * Инициализация зависимостей
188      *
189      * Params:
190      * container = Контейнер DI
191      *
192      * Example:
193      * ---
194      * initDependencies(container);
195      * ---
196      */
197     void initDependencies(shared(DependencyContainer) container, Properties config)
198     {
199     }
200 
201     /**
202      * Получение аргументов из командной строки
203      *
204      * Params:
205      * processor = Объект для разбора командной строки
206      *
207      * Returns: Успешность получения свойств
208      */
209     bool parseCommandLine(CommandLineProcessor processor)
210     {
211         return true;
212     }
213 
214     /**
215      * Возвращает строку помощи для консоли
216      */
217     string helpText() @property
218     {
219         return _applicationName;
220     }
221 
222 private:
223 
224     void doInitDependencies(shared(DependencyContainer) container, Properties config)
225     {
226         container.registerContext!PropertiesContext;
227         container.registerContext!LoggingContext;
228         container.register!(Application, typeof(this)).existingInstance(this);
229         container.register!PropertiesProxy.existingInstance(new PropertiesProxy(config));
230     }
231 
232 
233     bool doParseCommandLine(CommandLineProcessor processor)
234     {
235         processor.readOption("config|c", &_configFiles, "Конфигурационный файл");
236 
237         bool ret = parseCommandLine(processor);
238         ret &= processor.checkOptions();
239 
240         if (!ret)
241             processor.printer(helpText);
242 
243         return ret;
244     }
245 }
246 
247 
248 /**
249  * Приложение запускающее обработчик событий
250  * работающее в режиме демона
251  */
252 abstract class DaemonApplication : BaseApplication
253 {
254     this(string name, string _version)
255     {
256         super(name, _version);
257     }
258 
259 
260     this(string name, SemVer _version)
261     {
262         super(name, _version);
263     }
264 
265 protected:
266 
267     override final int runApplication(Properties config)
268     {
269         return runLoop(config);
270     }
271 
272     /**
273      * Запуск демона сервисов
274      * Params:
275      *
276      * config = Конфигурация приложения
277      */
278     void initializeDaemon(Properties config);
279 
280     /**
281      * Остановка демона сервисов
282      * Params:
283      *
284      * exitStatus = Код завершения приложения
285      */
286     int finalizeDaemon(int exitStatus);
287 
288 private:
289 
290     /**
291      * Запуск основного цикла обработки событий
292      * Params:
293      *
294      * config = Конфигурация приложения
295      */
296     int runLoop(Properties config)
297     {
298         logInfo("Запуск приложения %s (%s)", name, release);
299 
300         lowerPrivileges();
301 
302         initializeDaemon(config);
303 
304         logDiagnostic("Запуск цикла обработки событий...");
305         int status = runEventLoop();
306         logDiagnostic("Цикл событий зaвершен со статутом %d.", status);
307 
308         return finalizeDaemon(status);
309     }
310 }
311