UNPKG

13.8 kBJavaScriptView Raw
1'use strict';
2
3var _defaults = require('lodash/defaults');
4var Path = require('path');
5var Util = require('util');
6var Express = require('express');
7var UUID = require('uuid');
8var Diread = require('diread');
9
10var toArray = require('./helper/toArray');
11var deepFreeze = require('./helper/deepFreeze');
12var pathWithoutExtension = require('./helper/pathWithoutExtension');
13var requireWithSkippingOfMissedModuleError = require('./helper/requireWithSkippingOfMissedModuleError');
14
15var debug = require('debug')('ifnode:application'); // eslint-disable-line
16var Log = require('./Log');
17
18var Extension = require('./application/Extension');
19var PLUGIN_TYPES = require('./PLUGIN_TYPES');
20var SchemaFactory = require('./SchemaFactory');
21var ConfigurationBuilder = require('./ConfigurationBuilder');
22
23var SchemasList = require('./application/SchemasList');
24var DAOList = require('./application/DAOList');
25var ModelBuilder = require('./application/ModelBuilder');
26
27var ComponentsBuilder = require('./application/ComponentsBuilder');
28var ControllersBuilder = require('./application/ControllersBuilder');
29
30var Controller = require('./Controller');
31var RestMiddleware = require('./middleware/rest');
32
33var IFNodeVirtualSchema = require('./../plugins/ifnode-virtual');
34var NodeHTTPServer = require('./../plugins/node-http_s-server');
35
36/**
37 * Creates a new Application instance
38 *
39 * @class Application
40 *
41 * @param {ApplicationOptions} options
42 */
43function Application(options) {
44 if(options.alias !== void 0 && typeof options.alias !== 'string') {
45 Log.error('application', 'Alias must be String');
46 }
47
48 this._require_cache = {};
49 this._extensions_cache = {};
50 this._project_folder = options.project_folder || options.projectFolder || Path.dirname(process.argv[1]);
51 this._backend_folder = Path.resolve(this._project_folder, 'protected/');
52
53 this._modules = [
54 IFNodeVirtualSchema
55 ];
56 this._models_builder = null;
57 this._controllers_builder = null;
58
59 this.id = UUID.v4();
60 this.alias = options.alias || this.id;
61 this.project_folder = this.projectFolder = this._project_folder;
62 this.backend_folder = this.backendFolder = this._backend_folder;
63
64 this.config = this._initialize_config(options);
65 deepFreeze(this.config);
66
67 this.listener = this._initialize_listener();
68 this.connection = this._initialize_connection_server();
69
70 /**
71 * @deprecated Deprecated from 2.0.0 version. Connection server will be presented by app.connection
72 * @type {http.Server}
73 */
74 this.server = this.connection.server;
75
76 this.models = {};
77 this.components = {};
78 this._components_builder = new ComponentsBuilder(this.components, this.config.components);
79 this.controllers = {};
80}
81
82require('./application/middleware')(Application);
83
84/**
85 * Require module from start point like application project folder
86 *
87 * @param {string} id
88 * @returns {*}
89 */
90Application.prototype.require = function(id) {
91 if(!(id in this._require_cache)) {
92 this._require_cache[id] = require(Path.resolve(this.project_folder, id));
93 }
94
95 return this._require_cache[id];
96};
97
98/**
99 * Registered plugin(s) for application instance
100 *
101 * @param {string|Extension|Array.<string|Extension>} module
102 * @returns {Application}
103 */
104Application.prototype.register = function(module) {
105 var type_of = typeof module;
106
107 if(!(
108 type_of === 'string' ||
109 Array.isArray(module) ||
110 (type_of !== 'undefined' && type_of !== 'number')
111 )) {
112 Log.error('plugins', 'Wrong plugin type');
113 }
114
115 var self = this;
116 var modules = this._modules;
117
118 toArray(module).forEach(function(module) {
119 modules.push(typeof module === 'string' ?
120 self._require_module(module) :
121 module
122 );
123 });
124
125 return this;
126};
127
128/**
129 * @typedef {Object} ModuleLoadOptions
130 *
131 * @property {RegExp} [include]
132 * @property {RegExp} [exclude]
133 */
134
135/**
136 * @typedef {Object} ApplicationLoadOptions
137 *
138 * @property {boolean|ModuleLoadOptions} [controllers]
139 */
140
141/**
142 * Loads all maintenance parts of application
143 *
144 * @param {ApplicationLoadOptions} [options]
145 * @returns {Application}
146 */
147Application.prototype.load = function(options) {
148 options = _defaults(options, {
149 controllers: true
150 });
151
152 this.models = this._initialize_models();
153 Object.freeze(this.models);
154
155 this._initialize_components();
156 // Object.freeze(this.components);
157
158 if(options.controllers) {
159 this.controllers = options.controllers === true?
160 this._initialize_controllers() :
161 this._initialize_controllers(options.controllers);
162 Object.freeze(this.controllers);
163 }
164
165 this._is_loaded = true;
166
167 return this;
168};
169
170/**
171 *
172 * @param {string} id
173 * @returns {*}
174 */
175Application.prototype.extension = function(id) {
176 var cache = this._extensions_cache;
177
178 if(!(id in cache)) {
179 var custom_folder = this.config.application.folders.extensions;
180 var custom_full_path = Path.resolve(this._project_folder, custom_folder);
181 var extension_loader = new Extension(custom_full_path);
182
183 cache[id] = extension_loader.require(id);
184 }
185
186 return cache[id];
187};
188
189/**
190 *
191 * @param {string} id
192 * @returns {*}
193 */
194Application.prototype.ext = Util.deprecate(
195 Application.prototype.extension,
196 'Deprecated from 2.0.0 version. Needs to use app.extension() method'
197);
198
199/**
200 *
201 * @param {string} id
202 * @returns {Object}
203 */
204Application.prototype.component = function(id) {
205 if(id in this.components) {
206 return this.components[id];
207 }
208
209 var full_path = Path.resolve(this.config.application.folders.components, id);
210 var components_configs = this.config.components;
211 var components_builder = this._components_builder;
212 var component = components_builder.read_and_build_component(full_path, {
213 name: id,
214 config: (components_configs && components_configs[id]) || {}
215 });
216
217 components_builder.compile(this, full_path);
218
219 if (!this.components[id]) {
220 components_builder.save_component(component, id);
221 components_builder.components_compiled[id] = true;
222 }
223
224 return component;
225};
226
227/**
228 *
229 * @param {Object} model_config
230 * @param {Object} [options]
231 * @returns {Function}
232 * @constructor
233 */
234Application.prototype.Model = function(model_config, options) {
235 return this._models_builder.make(model_config, options);
236};
237
238/**
239 *
240 * @param {Object} [custom_component_config]
241 * @returns {Component}
242 */
243Application.prototype.Component = function(custom_component_config) {
244 var builder = this._components_builder;
245
246 return builder.make(
247 builder.build_component_config(custom_component_config)
248 );
249};
250
251/**
252 *
253 * @param {Object} [controller_config]
254 * @returns {Controller}
255 */
256Application.prototype.Controller = function(controller_config) {
257 return this._controllers_builder.make(controller_config);
258};
259
260/**
261 * Start web-server
262 *
263 * @deprecated
264 * @param {function} callback
265 */
266Application.prototype.run = Util.deprecate(function(callback) {
267 if(!this._is_loaded) {
268 this.load();
269 }
270
271 var self = this;
272 var connection = this.connection;
273
274 if(typeof callback === 'function') {
275 connection.listen(function() {
276 callback.call(self, self.config);
277 });
278 } else {
279 connection.listen();
280 }
281}, 'Deprecated from 2.0.0 version. Server will started by app.connection.listen()');
282
283/**
284 * Stop web-server
285 *
286 * @deprecated
287 * @param {function} callback
288 */
289Application.prototype.down = Util.deprecate(function(callback) {
290 this.connection.close(callback);
291}, 'Deprecated from 2.0.0 version. Server will be stopped by app.connection.close()');
292
293/**
294 * Initialize application instance configuration
295 *
296 * @private
297 * @param {ApplicationOptions} options
298 */
299Application.prototype._initialize_config = function(options) {
300 var environment = options.env || options.environment;
301 var configuration = options.configuration;
302 var custom_configuration = null;
303
304 if(environment) {
305 custom_configuration = require(Path.resolve(this._project_folder, 'config/', environment));
306 } else if (configuration) {
307 custom_configuration = typeof configuration === 'string' ?
308 require(Path.resolve(this._project_folder, configuration)) :
309 configuration;
310 }
311
312 return ConfigurationBuilder({
313 project_folder: this._project_folder,
314 backend_folder: this._backend_folder,
315 custom_configuration: custom_configuration
316 });
317};
318
319/**
320 * Initialize server listener
321 *
322 * @returns {Express}
323 * @private
324 */
325Application.prototype._initialize_listener = function() {
326 var app = Express();
327 var config = this.config;
328 var app_config = config.application;
329
330 var middleware_configs = app_config.middleware;
331
332 app.use(RestMiddleware.response());
333 if(middleware_configs) {
334 this._initialize_middleware(middleware_configs, app);
335 }
336 app.use(RestMiddleware.request());
337
338 var express_configs = app_config.express;
339
340 Object.keys(express_configs).forEach(function(express_option) {
341 app.set(express_option, express_configs[express_option]);
342 });
343
344 return app;
345};
346
347/**
348 * Initialize native node.js (http,https, etc) server
349 *
350 * @returns {IConnectionServer}
351 * @private
352 */
353Application.prototype._initialize_connection_server = function() {
354 var site_config = this.config.site;
355 var connection = site_config.connection;
356
357 switch(connection) {
358 case 'http':
359 case 'https':
360 return new NodeHTTPServer(this.listener, site_config);
361
362 default:
363 /**
364 *
365 * @class IConnectionServer
366 */
367 var ServerCreator = this._require_module(connection);
368
369 return new ServerCreator(this.listener, site_config);
370 }
371};
372
373/**
374 *
375 * @private
376 */
377Application.prototype._initialize_models = function _initialize_models() {
378 var db = this.config.db;
379 var modules = this._modules;
380 var schemas_list = new SchemasList;
381
382 for(var i = 0; i < modules.length; ++i) {
383 var module = modules[i][PLUGIN_TYPES.SCHEMA];
384
385 if(module) {
386 var schema = SchemaFactory();
387
388 module(this, schema);
389 schemas_list.attach_schema(schema);
390 }
391 }
392
393 var models_builder = this._models_builder = new ModelBuilder(
394 new DAOList(schemas_list, db)
395 );
396
397 Diread({
398 src: this.config.application.folders.models
399 }).each(function(model_file_path) {
400 require(model_file_path);
401 });
402
403 return models_builder.compile_models(this);
404};
405
406/**
407 *
408 * @returns {Object.<string, Component>}
409 * @private
410 */
411Application.prototype._initialize_components = function _initialize_components() {
412 var self = this;
413 var components_builder = this._components_builder;
414 var Component = this.Component.bind(this);
415 var modules = this._modules;
416
417 for(var i = 0; i < modules.length; ++i) {
418 var module = modules[i][PLUGIN_TYPES.COMPONENT];
419
420 if(module) {
421 var component = module(this, Component);
422
423 if(component) {
424 components_builder.build_component(component, {});
425 }
426
427 components_builder.compile(this);
428 }
429 }
430
431 Diread({
432 src: this.config.application.folders.components,
433 directories: true,
434 level: 1,
435 mask: function(path) {
436 return path === pathWithoutExtension(path) ||
437 path.indexOf('.js') !== -1;
438 }
439 }).each(function(component_path) {
440 var autoformed_config = components_builder.build_and_memorize_config(component_path);
441
442 try {
443 components_builder.read_and_build_component(
444 component_path,
445 components_builder.build_component_config()
446 );
447 components_builder.compile(self, component_path);
448 } catch(error) {
449 /**
450 * Errors inside component will not catch by this handle
451 */
452 if(error.message.indexOf(component_path) === -1) {
453 throw error;
454 } else {
455 Log.warning(
456 'components',
457 'Cannot load component [' + autoformed_config.name + '] by path [' + component_path + ']'
458 );
459 }
460 }
461 });
462};
463
464/**
465 *
466 * @private
467 * @param {ModuleLoadOptions} [options]
468 */
469Application.prototype._initialize_controllers = function _initialize_controllers(options) {
470 var controllers_builder = this._controllers_builder = new ControllersBuilder(options);
471 var modules = this._modules;
472
473 for(var i = 0; i < modules.length; ++i) {
474 var module = modules[i][PLUGIN_TYPES.CONTROLLER];
475
476 if(module) {
477 module(this, Controller);
478 }
479 }
480
481 controllers_builder.read_and_initialize_controllers(
482 this.config.application.folders.controllers, this
483 );
484
485 return controllers_builder.compile(this.listener);
486};
487
488/**
489 * Require node_module or application extension
490 *
491 * @returns {*}
492 * @private
493 */
494Application.prototype._require_module = function(module_name) {
495 var Module;
496
497 Module = requireWithSkippingOfMissedModuleError(module_name);
498
499 if(Module) {
500 return Module;
501 }
502
503 var self = this;
504
505 try {
506 Module = this.extension(module_name);
507 } catch (error) {
508 if (!/Cannot\sfind\sextension/.test(error.message)) {
509 throw error;
510 }
511 }
512
513 if(Module) {
514 return Module;
515 }
516
517 Log.error('application', 'Cannot find node module or extension [' + module_name + '].');
518};
519
520module.exports = Application;