UNPKG

11.1 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 inaccessible.');
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 inaccessible.');
70 }
71 else {
72 TraceUtils.error('An error occurred 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 occurred 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} strategyBaseCtor
168 * @param {Function=} strategyCtor
169 * @returns ConfigurationBase
170 */
171ConfigurationBase.prototype.useStrategy = function(strategyBaseCtor, strategyCtor) {
172 Args.notFunction(strategyBaseCtor,"Configuration strategy constructor");
173 if (typeof strategyCtor === 'undefined') {
174 this[strategiesProperty]["$".concat(strategyBaseCtor.name)] = new strategyBaseCtor(this);
175 return this;
176 }
177 Args.notFunction(strategyCtor,"Strategy constructor");
178 this[strategiesProperty]["$".concat(strategyBaseCtor.name)] = new strategyCtor(this);
179 return this;
180};
181//noinspection JSUnusedGlobalSymbols
182/**
183 * Gets a configuration strategy
184 * @param {Function} strategyBaseCtor
185 */
186ConfigurationBase.prototype.getStrategy = function(strategyBaseCtor) {
187 Args.notFunction(strategyBaseCtor,"Configuration strategy constructor");
188 return this[strategiesProperty]["$".concat(strategyBaseCtor.name)];
189};
190
191/**
192 * Gets a configuration strategy
193 * @param {Function} strategyBaseCtor
194 */
195ConfigurationBase.prototype.hasStrategy = function(strategyBaseCtor) {
196 Args.notFunction(strategyBaseCtor,"Configuration strategy constructor");
197 return typeof this[strategiesProperty]["$".concat(strategyBaseCtor.name)] !== 'undefined';
198};
199
200/**
201 * Gets the current configuration
202 * @returns ConfigurationBase - An instance of DataConfiguration class which represents the current data configuration
203 */
204ConfigurationBase.getCurrent = function() {
205 if (_.isNil(ConfigurationBase[currentConfiguration])) {
206 ConfigurationBase[currentConfiguration] = new ConfigurationBase();
207 }
208 return ConfigurationBase[currentConfiguration];
209};
210/**
211 * Sets the current configuration
212 * @param {ConfigurationBase} configuration
213 * @returns ConfigurationBase - An instance of ApplicationConfiguration class which represents the current configuration
214 */
215ConfigurationBase.setCurrent = function(configuration) {
216 if (configuration instanceof ConfigurationBase) {
217 if (!configuration.hasStrategy(ModuleLoaderStrategy)) {
218 configuration.useStrategy(ModuleLoaderStrategy, DefaultModuleLoaderStrategy);
219 }
220 ConfigurationBase[currentConfiguration] = configuration;
221 return ConfigurationBase[currentConfiguration];
222 }
223 throw new TypeError('Invalid argument. Expected an instance of DataConfiguration class.');
224};
225
226/**
227 * @class
228 * @param {ConfigurationBase} config
229 * @constructor
230 * @abstract
231 */
232function ConfigurationStrategy(config) {
233 Args.check(this.constructor.name !== ConfigurationStrategy, new AbstractClassError());
234 Args.notNull(config, 'Configuration');
235 this[configProperty] = config;
236}
237
238/**
239 * @returns {ConfigurationBase}
240 */
241ConfigurationStrategy.prototype.getConfiguration = function() {
242 return this[configProperty];
243};
244
245/**
246 * @class
247 * @constructor
248 * @param {ConfigurationBase} config
249 * @extends ConfigurationStrategy
250 */
251function ModuleLoaderStrategy(config) {
252 ModuleLoaderStrategy.super_.bind(this)(config);
253}
254LangUtils.inherits(ModuleLoaderStrategy, ConfigurationStrategy);
255
256ModuleLoaderStrategy.prototype.require = function(modulePath) {
257 Args.notEmpty(modulePath,'Module Path');
258 if (!/^.\//i.test(modulePath)) {
259 if (require.resolve && require.resolve.paths) {
260 /**
261 * get require paths collection
262 * @type string[]
263 */
264 var paths = require.resolve.paths(modulePath);
265 //get execution
266 var path1 = this.getConfiguration().getExecutionPath();
267 //loop directories to parent (like classic require)
268 while (path1) {
269 //if path does not exist in paths collection
270 if (paths.indexOf(PathUtils.join(path1,'node_modules'))<0) {
271 //add it
272 paths.push(PathUtils.join(path1,'node_modules'));
273 //and check the next path which is going to be resolved
274 if (path1 === PathUtils.join(path1,'..')) {
275 //if it is the same with the current path break loop
276 break;
277 }
278 //otherwise get parent path
279 path1 = PathUtils.join(path1,'..');
280 }
281 else {
282 //path already exists in paths collection, so break loop
283 break;
284 }
285 }
286 var finalModulePath = require.resolve(modulePath, {
287 paths:paths
288 });
289 return require(finalModulePath);
290 }
291 else {
292 return require(modulePath);
293 }
294 }
295 return require(PathUtils.join(this.getConfiguration().getExecutionPath(),modulePath));
296};
297
298/**
299 * @class
300 * @constructor
301 * @param {ConfigurationBase} config
302 * @extends ModuleLoaderStrategy
303 */
304function DefaultModuleLoaderStrategy(config) {
305 DefaultModuleLoaderStrategy.super_.bind(this)(config);
306}
307LangUtils.inherits(DefaultModuleLoaderStrategy, ModuleLoaderStrategy);
308
309
310
311if (typeof exports !== 'undefined') {
312 module.exports.ConfigurationBase = ConfigurationBase;
313 module.exports.ConfigurationStrategy = ConfigurationStrategy;
314 module.exports.ModuleLoaderStrategy = ModuleLoaderStrategy;
315 module.exports.DefaultModuleLoaderStrategy = DefaultModuleLoaderStrategy;
316}
317
318