UNPKG

11 kBJavaScriptView Raw
1/**
2 * @license
3 * MOST Web Framework 2.0 Codename Blueshift
4 * Copyright (c) 2017, THEMOST LP All rights reserved
5 *
6 * Use of this source code is governed by an BSD-3-Clause license that can be
7 * found in the LICENSE file at https://themost.io/license
8 */
9///
10var _ = require('lodash');
11var Symbol = require('symbol');
12var LangUtils = require("./utils").LangUtils;
13var Args = require('./utils').Args;
14var TraceUtils = require('./utils').TraceUtils;
15var PathUtils = require('./utils').PathUtils;
16var AbstractClassError = require('./errors').AbstractClassError;
17
18var configProperty = Symbol('config');
19var currentConfiguration = Symbol('current');
20var configPathProperty = Symbol('configurationPath');
21var executionPathProperty = Symbol('executionPath');
22var strategiesProperty = Symbol('strategies');
23
24/**
25 * @class Represents an application configuration
26 * @param {string=} configPath
27 * @property {*} settings
28 * @constructor
29 */
30function ConfigurationBase(configPath) {
31 //init strategies
32 this[strategiesProperty] = { };
33
34 this[configPathProperty] = configPath || PathUtils.join(process.cwd(),'config');
35 TraceUtils.debug('Initializing configuration under %s.', this[configPathProperty]);
36
37 this[executionPathProperty] = PathUtils.join(this[configPathProperty],'..');
38 TraceUtils.debug('Setting execution path under %s.', this[executionPathProperty]);
39
40 //load default module loader strategy
41 this.useStrategy(ModuleLoaderStrategy, DefaultModuleLoaderStrategy);
42
43 //get configuration source
44 var configSourcePath;
45 try {
46 var env = 'production';
47 //node.js mode
48 if (process && process.env) {
49 env = process.env['NODE_ENV'] || 'production';
50 }
51 //browser mode
52 else if (window && window.env) {
53 env = window.env['BROWSER_ENV'] || 'production';
54 }
55 configSourcePath = PathUtils.join(this[configPathProperty], 'app.' + env + '.json');
56 TraceUtils.debug('Validating environment configuration source on %s.', configSourcePath);
57 this[configProperty] = require(configSourcePath);
58 }
59 catch (err) {
60 if (err.code === 'MODULE_NOT_FOUND') {
61 TraceUtils.log('The environment specific configuration cannot be found or is inaccesible.');
62 try {
63 configSourcePath = PathUtils.join(this[configPathProperty], 'app.json');
64 TraceUtils.debug('Validating application configuration source on %s.', configSourcePath);
65 this[configProperty] = require(configSourcePath);
66 }
67 catch(err) {
68 if (err.code === 'MODULE_NOT_FOUND') {
69 TraceUtils.log('The default application configuration cannot be found or is inaccesible.');
70 }
71 else {
72 TraceUtils.error('An error occured while trying to open default application configuration.');
73 TraceUtils.error(err);
74 }
75 TraceUtils.debug('Initializing empty configuration');
76 this[configProperty] = { };
77 }
78 }
79 else {
80 TraceUtils.error('An error occured while trying to open application configuration.');
81 TraceUtils.error(err);
82 //load default configuration
83 this[configProperty] = { };
84 }
85 }
86 //initialize settings object
87 this[configProperty]['settings'] = this[configProperty]['settings'] || { };
88
89 /**
90 * @name ConfigurationBase#settings
91 * @type {*}
92 */
93
94 Object.defineProperty(this, 'settings',{
95 get: function() {
96 return this[configProperty]['settings'];
97 },
98 enumerable:true,
99 configurable:false});
100
101}
102//noinspection JSUnusedGlobalSymbols
103/**
104 * Returns the configuration source object
105 * @returns {*}
106 */
107ConfigurationBase.prototype.getSource = function() {
108 return this[configProperty];
109};
110//noinspection JSUnusedGlobalSymbols
111/**
112 * Returns the source configuration object based on the given path (e.g. settings.auth.cookieName or settings/auth/cookieName)
113 * @param {string} p - A string which represents an object path
114 * @returns {Object|Array}
115 */
116ConfigurationBase.prototype.getSourceAt = function(p) {
117 return _.at(this[configProperty],p.replace(/\//g,'.'))[0];
118};
119//noinspection JSUnusedGlobalSymbols
120/**
121 * Returns a boolean which indicates whether the specified object path exists or not (e.g. settings.auth.cookieName or settings/auth/cookieName)
122 * @param {string} p - A string which represents an object path
123 * @returns {boolean}
124 */
125ConfigurationBase.prototype.hasSourceAt = function(p) {
126 return _.isObject(_.at(this[configProperty],p.replace(/\//g,'.'))[0]);
127};
128//noinspection JSUnusedGlobalSymbols
129/**
130 * Sets the config value to the specified object path (e.g. settings.auth.cookieName or settings/auth/cookieName)
131 * @param {string} p - A string which represents an object path
132 * @param {*} value
133 * @returns {Object}
134 */
135ConfigurationBase.prototype.setSourceAt = function(p, value) {
136 return _.set(this[configProperty], p.replace(/\//g,'.'), value);
137};
138//noinspection JSUnusedGlobalSymbols
139/**
140 * Sets the current execution path
141 * @param {string} p
142 * @returns ConfigurationBase
143 */
144ConfigurationBase.prototype.setExecutionPath = function(p) {
145 this[executionPathProperty] = p;
146 return this;
147};
148
149/**
150 * Gets the current execution path
151 * @returns {string}
152 */
153ConfigurationBase.prototype.getExecutionPath = function() {
154 return this[executionPathProperty];
155};
156
157/**
158 * Gets the current configuration path
159 * @returns {string}
160 */
161ConfigurationBase.prototype.getConfigurationPath = function() {
162 return this[configPathProperty];
163};
164
165/**
166 * Register a configuration strategy
167 * @param {Function} configStrategyCtor
168 * @param {Function} strategyCtor
169 * @returns ConfigurationBase
170 */
171ConfigurationBase.prototype.useStrategy = function(configStrategyCtor, strategyCtor) {
172 Args.notFunction(configStrategyCtor,"Configuration strategy constructor");
173 Args.notFunction(strategyCtor,"Strategy constructor");
174 this[strategiesProperty]["$".concat(configStrategyCtor.name)] = new strategyCtor(this);
175 return this;
176};
177//noinspection JSUnusedGlobalSymbols
178/**
179 * Gets a configuration strategy
180 * @param {Function} configStrategyCtor
181 */
182ConfigurationBase.prototype.getStrategy = function(configStrategyCtor) {
183 Args.notFunction(configStrategyCtor,"Configuration strategy constructor");
184 return this[strategiesProperty]["$".concat(configStrategyCtor.name)];
185};
186
187/**
188 * Gets a configuration strategy
189 * @param {Function} configStrategyCtor
190 */
191ConfigurationBase.prototype.hasStrategy = function(configStrategyCtor) {
192 Args.notFunction(configStrategyCtor,"Configuration strategy constructor");
193 return typeof this[strategiesProperty]["$".concat(configStrategyCtor.name)] !== 'undefined';
194};
195
196/**
197 * Gets the current configuration
198 * @returns ConfigurationBase - An instance of DataConfiguration class which represents the current data configuration
199 */
200ConfigurationBase.getCurrent = function() {
201 if (_.isNil(ConfigurationBase[currentConfiguration])) {
202 ConfigurationBase[currentConfiguration] = new ConfigurationBase();
203 }
204 return ConfigurationBase[currentConfiguration];
205};
206/**
207 * Sets the current configuration
208 * @param {ConfigurationBase} configuration
209 * @returns ConfigurationBase - An instance of ApplicationConfiguration class which represents the current configuration
210 */
211ConfigurationBase.setCurrent = function(configuration) {
212 if (configuration instanceof ConfigurationBase) {
213 if (!configuration.hasStrategy(ModuleLoaderStrategy)) {
214 configuration.useStrategy(ModuleLoaderStrategy, DefaultModuleLoaderStrategy);
215 }
216 ConfigurationBase[currentConfiguration] = configuration;
217 return ConfigurationBase[currentConfiguration];
218 }
219 throw new TypeError('Invalid argument. Expected an instance of DataConfiguration class.');
220};
221
222/**
223 * @class
224 * @param {ConfigurationBase} config
225 * @constructor
226 * @abstract
227 */
228function ConfigurationStrategy(config) {
229 Args.check(this.constructor.name !== ConfigurationStrategy, new AbstractClassError());
230 Args.notNull(config, 'Configuration');
231 this[configProperty] = config;
232}
233
234/**
235 * @returns {ConfigurationBase}
236 */
237ConfigurationStrategy.prototype.getConfiguration = function() {
238 return this[configProperty];
239};
240
241/**
242 * @class
243 * @constructor
244 * @param {ConfigurationBase} config
245 * @extends ConfigurationStrategy
246 */
247function ModuleLoaderStrategy(config) {
248 ModuleLoaderStrategy.super_.bind(this)(config);
249}
250LangUtils.inherits(ModuleLoaderStrategy, ConfigurationStrategy);
251
252ModuleLoaderStrategy.prototype.require = function(modulePath) {
253 Args.notEmpty(modulePath,'Module Path');
254 if (!/^.\//i.test(modulePath)) {
255 if (require.resolve && require.resolve.paths) {
256 /**
257 * get require paths collection
258 * @type string[]
259 */
260 let paths = require.resolve.paths(modulePath);
261 //get execution
262 let path1 = this.getConfiguration().getExecutionPath();
263 //loop directories to parent (like classic require)
264 while (path1) {
265 //if path does not exist in paths collection
266 if (paths.indexOf(PathUtils.join(path1,'node_modules'))<0) {
267 //add it
268 paths.push(PathUtils.join(path1,'node_modules'));
269 //and check the next path which is going to be resolved
270 if (path1 === PathUtils.join(path1,'..')) {
271 //if it is the same with the current path break loop
272 break;
273 }
274 //otherwise get parent path
275 path1 = PathUtils.join(path1,'..');
276 }
277 else {
278 //path already exists in paths collection, so break loop
279 break;
280 }
281 }
282 let finalModulePath = require.resolve(modulePath, {
283 paths:paths
284 });
285 return require(finalModulePath);
286 }
287 else {
288 return require(modulePath);
289 }
290 }
291 return require(PathUtils.join(this.getConfiguration().getExecutionPath(),modulePath));
292};
293
294/**
295 * @class
296 * @constructor
297 * @param {ConfigurationBase} config
298 * @extends ModuleLoaderStrategy
299 */
300function DefaultModuleLoaderStrategy(config) {
301 DefaultModuleLoaderStrategy.super_.bind(this)(config);
302}
303LangUtils.inherits(DefaultModuleLoaderStrategy, ModuleLoaderStrategy);
304
305
306
307if (typeof exports !== 'undefined') {
308 module.exports.ConfigurationBase = ConfigurationBase;
309 module.exports.ConfigurationStrategy = ConfigurationStrategy;
310 module.exports.ModuleLoaderStrategy = ModuleLoaderStrategy;
311 module.exports.DefaultModuleLoaderStrategy = DefaultModuleLoaderStrategy;
312}
313
314