Show:

File: platform/classes/Q/Bootstrap.js

/**
 * @module Q
 */
var Q = require('../Q');
/**
 * Define properties and methods needed to load and initialize platform classes and config
 * @class Bootstrap
 * @namespace Q
 * @static
 */
var Bootstrap = {};

/**
 * Add path to the list of paths checked by node.js while searching a module
 * @method setIncludePath
 * @param {string} path
 * @param {boolean} whether a new path was added
 */
Bootstrap.setIncludePath = function (path) {
	var paths = (process.env.NODE_PATH || '').split(Q.PS);
	if (!path) {
		paths.unshift(Q.app.CLASSES_DIR, Q.CLASSES_DIR);
	} else if (paths.indexOf(path) >= 0) {
		return false;
	} else {
		paths.splice(1, 0, path);
	}
	var normalize = require('path').normalize;
	for (var i = 0, l = paths.length; i<l; ++i) {
		paths[i] = normalize(paths[i]);
	}
	process.env.NODE_PATH = paths.join(Q.PS);
	require('module')._initPaths();
	return true;
};

/**
 * @method registerExceptionHandler
 */
Bootstrap.registerExceptionHandler = function() {
	process.on('uncaughtException', Q.exceptionHandler);
};

var _reloadConfig;

/**
 * Load platform configuration
 * @method configure
 * @param {function} callback Callback is fired when config is ready. 
 *  Errors are thrown if "reload" is false and passed to "callback" if "reload" is true
 * @param {boolean} [reload=false] Wheather config is loaded on process start or reloaded by active process
 * @throws {Error} if reload is `false` throws any errors appearing during config load process, otherwise
 * 	passes them to callback
 */
Bootstrap.configure = function (callback, reload) {
	if (reload) {
		clearTimeout(_reloadConfig); // if called manually clear the loop
	}
	var c = callback;
	callback = function () {
		process.env.TZ = Q.Config.expect(['Q', 'defaultTimezone']);
		c.apply(this, arguments);
	};
	var Config = new Q.Tree();
	var pluginInfo = {};
	var p = new Q.Pipe(['Q_config', 'app_merged'], function (params) {
		for (var k in params) {
			if (params[k][0]) {
				if (reload) {
					callback && callback(params[k][0]);
					return;
				} else throw params[k][0];
			}
		}
		var app_merged = params.app_merged[1];
		if (app_merged.Q.plugins && app_merged.Q.plugins.length) {
			var plugins = [];
			for (var i in app_merged.Q.plugins) {
				plugins.push(app_merged.Q.plugins[i]);
			}
			if (app_merged.Q.plugins.indexOf('Q') < 0) {
				app_merged.Q.plugins.unshift('Q');
			}
			var filenames = [];
			for (i in app_merged.Q.plugins) {
				var plugin = app_merged.Q.plugins[i];
				var tree = new Q.Tree();
				filenames.push(Q.PLUGINS_DIR+'/'+plugin+'/config/plugin.json');
				var dirs = {
					CLASSES_DIR: 'classes',
					CONFIG_DIR: 'config',
					FILES_DIR: 'files',
					HANDLERS_DIR: 'handlers',
					SCRIPTS_DIR: 'scripts',
					TESTS_DIR: 'tests',
					VIEWS_DIR: 'views'
				};
				pluginInfo[plugin] = {};
				for (var dir in dirs) {
					pluginInfo[plugin][dir] = Q.PLUGINS_DIR+'/'+plugin+'/'+dirs[dir];
				}
				Bootstrap.setIncludePath(Q.PLUGINS_DIR+'/'+plugin+'/classes');
			}
			Q.pluginInfo = pluginInfo;
			// load and merge all the plugin config files
			Config.load(filenames, function (err) {
				// finally, merge the app_merged config on top
				_merge_app_config(err);
			});
		} else {
			_merge_app_config();
		}
		function _startConfigLoop() {
			var timeout = Q.Config.get(['Q', 'internal', 'configServer', 'interval'], 60)*100;
			if (timeout)
				_reloadConfig = setTimeout(function () {
					Bootstrap.configure(function (err) {
						if (err) Q.emit('Config/reload', err);
					}, true);
				}, timeout);
			else
				Bootstrap.configure(function (err) {
					if (err) Q.emit('Config/reload', err);
				}, true);
		}
		function _loadConfigExtras (callback) {
			// Now, load any other files we were supposed to load
			var config_files = Config.get(['Q', 'configFiles'], []);
			if (config_files.length) {
				var p = new Q.Pipe(config_files, function (params) {
					for (var k in params) {
						if (params[k][0]) throw params[k][0];
						else Config.merge(params[k][1]);
					}
					// second round to catch configFiles inside configFiles
					config_files = Config.get(['Q', 'configFiles'], []);
					if (config_files.length) {
						var p = new Q.Pipe(config_files, function(params) {
							for (var k in params) {
								if (params[k][0]) throw params[k][0];
								else Config.merge(params[k][1]);
							}
							callback && callback();
						});
						for (var i=0; i<config_files.length; i++) {
							Q.Config.getFromServer(config_files[i], p.fill(config_files[i]));
						}
					}
				});
				for (var i=0; i<config_files.length; i++) {
					Q.Config.getFromServer(config_files[i], p.fill(config_files[i]));
				}
			} else {
				callback && callback();
			}
		}
		function _merge_app_config (err) {
			if (err) {
				if (reload) return callback && callback(err);
				throw err;
			}
			Config.merge(app_merged);
			if (reload) {
				try {
					_loadConfigExtras(function () {
						Q.Config.clear(); // clear the config
						Q.Config.set(Config.getAll());
						// TODO: THIS LEAKS MEMORY!  FIX IT
						// THANKSFULLY WE DON'T NEED IT FOR NOW
						// _startConfigLoop();
						/**
						 * Config tree hs been reloaded
						 * @event Config/reload
						 * @param {Error} error
						 *	The error object if any
						 */
						Q.emit('Config/reload', null);
						callback && callback();
					});
				} catch (err) {
					callback && callback(err);
				}
			}
			else {
				// start config server listener before loading other files
				Q.Config.clear();
				Q.Config.set(Config.getAll());
				Q.Config.listen(function() {
					_loadConfigExtras(function() {
						Q.Config.clear(); // clear the config to make merge faster
						Q.Config.set(Config.getAll());
						_startConfigLoop();
						callback && callback();
					});
				});
			}
		}
	});
	Config.load(Q.CONFIG_DIR+'/Q.json', p.fill('Q_config'));
	var app_merged = new Q.Tree();
	app_merged.load([
		Q.app.CONFIG_DIR+'/app.json',
		Q.app.LOCAL_DIR+'/app.json'
	], p.fill('app_merged'));
};

/**
 * Add plugins to Q.plugins namespace
 * @method loadPlugins
 * @param {function} callback
 */
Bootstrap.loadPlugins = function (callback) {
	Q.plugins = {};
	var plugins = Q.Config.get(['Q', 'plugins'], []);
	var len = plugins.length;
	for (var i=0; i<len; ++i) {
		if (plugins[i] === 'Q') {
			continue;
		}
		var pluginName = plugins[i];
		Q.plugins[pluginName] = Q.require(pluginName);
	}
	callback();
};

/**
 * Load handlers available in the platform and plugins
 * @method loadHandlers
 * @param {function} callback
 */
Bootstrap.loadHandlers = function (callback) {
	Q.handlers = {};
	var plugins = Q.Config.get(['Q', 'plugins'], []);
	while (plugins.indexOf('Q') >= 0) {
		plugins.splice(plugins.indexOf('Q'), 1);
	}
	// first load Q handlers
	loadHandlers(Q.HANDLERS_DIR, function() {
		// now load plugins handlers one by one
		loadHandlers(plugins.map(function (a) { 
			return Q.PLUGINS_DIR+'/'+a+'/handlers';
		}), function () {
			// finally load application handlers
			loadHandlers(Q.app.HANDLERS_DIR, callback);
		});
	});
	function loadHandlers(dir, cb) {
		if (typeof dir !== "string") {
			if (dir.length > 0) {
				loadHandlers(dir.shift(), function () {
					loadHandlers(dir, cb);
				});
			} else cb();
		} else {
			Q.dir(dir, function (err, result) {
				// if (err) throw new Error(err);
				if (!result || !result.files) {
					cb();
					return;
				}
				var len = dir.length + 1;
				for (var i = 0; i<result.files.length; ++i) {
					var filename = result.files[i];
					if (filename.substr(-3) !== '.js') {
						continue;
					}
					var parts = filename.substring(len, filename.length-3).split(Q.DS);
					var handler = Q.require(filename);
					var obj = Q.handlers;
					for (var j=0; j<parts.length-1; ++j) {
						if (!obj [parts[j] ] ) {
							obj[ parts[j] ] = {};
						}
						obj = obj[ parts[j] ];
					}
					obj[ parts[parts.length-1] ] = handler;
				}
				cb();
			});
		}
	}
};

module.exports = Bootstrap;