var path = require('path'); var fs = require('fs'); var debug = require('debug')('ff:tcrud:config:registry'); var configUtil = require('./config-util'); var mod = (function () { var phaseInit = true; var phaseConfig = false; var phaseServer = false; var phaseServerUp = false; // Master template for plugins. this.masterTemplates = { masterTFieldTemplate: {}, masterTFieldTHelp: {}, masterTEntityTemplate: {}, masterTEntityTHelp: {}, masterTViewTemplate: {}, masterTViewTHelp: {} }; // Master config of tcrud this.masterConfig = { rootTEntity: null, rootTMenu: {}, plugins: [], backends: {}, validators: {}, clientResources: { js: [], css: [], dss: [] } }; this.pluginKeysAllowed = [ 'configPlugin', 'configTemplate', 'configServer', 'configApi', 'configApiTListExport', 'configApiTCreateExport', 'configApiTReadExport', 'configApiTEditExport', 'configApiTDeleteExport', 'configApiTCountExport', 'configPostBoot', 'fillTEntity', 'fillTField', 'fillTView', 'createBackend', 'key','dbModule','query','conn']; var mergeMaster = function(objectDest,pluginKey) { return function(objectSrc) { //console.log('mergeMasterSrc:'+JSON.stringify(objectSrc)); //console.log('mergeMasterDst:'+JSON.stringify(objectDest)); var resPre = {keys: 0,values: 0}; configUtil.countTree(objectDest,resPre); configUtil.copyByTemplate('',objectDest,objectSrc,objectSrc); var resPost = {keys: 0,values: 0}; configUtil.countTree(objectDest,resPost); // TODO: check pre+post counters so first 2 arrays grow equal ! debug('mergeMaster' +' start: '+resPre.keys+' -> '+resPre.values +' end: '+resPost.keys+' -> '+resPost.values +' diff: '+(resPost.keys-resPre.keys)+' -> '+(resPost.values-resPre.values) +' plugin: '+pluginKey); } }; var tentityMergeDown = function(prefix,objectDest,objectSrc) { var keys = Object.keys(objectSrc); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = objectSrc[key]; if (!value) { //console.log(prefix+'.'+key+' src object has no value.'); continue; // no src value } if (!objectDest[key]) { objectDest[key] = configUtil.clone(value); } else if (typeof value === 'object') { if (key === 'tchilds') { continue; } //console.log(prefix+'.'+key+' going deeper'); tentityMergeDown(prefix+'.'+key,objectDest[key],value) } } }; var tentityFillTree = function(parent) { debug('tentityFillTree.tid: %s',parent.tid); for (var key in parent.tmeta.tfields) { pluginCall('fillTField',{ filterValue: function(a,b,c,d) {return configUtil.filterValue(a,b,c,d);}, tfield: parent.tmeta.tfields[key] }); } for (var i = 0; i < parent.tchilds.length; i++) { var child = parent.tchilds[i]; tentityMergeDown('',child,parent); tentityFillTree(child); } pluginCall('fillTEntity',{ filterValue: function(a,b,c,d) {return configUtil.filterValue(a,b,c,d);}, tentity: parent }); }; var pluginCall = function(fnName,ctx,afterPluginCb) { var startTime = new Date().getTime(); var callCount = 0; var keys = Object.keys(masterConfig.plugins); for (var i = 0; i < keys.length; i++) { var pluginKey = keys[i]; var plugin = masterConfig.plugins[pluginKey]; if (plugin.tenable === false) { continue; } ctx.tplugin = plugin; if (plugin[fnName]) { var result = plugin[fnName](ctx); callCount++; if (afterPluginCb !== undefined) { afterPluginCb(plugin, result); } } } debug('pluginCall.function: %s done %s plugins in %s ms.',fnName,callCount,(new Date().getTime()-startTime)); }; var assertPhaseInit = function() { if (!phaseInit) { throw Error('Not in init phase.') } }; var assertPhaseConfig = function() { if (!phaseConfig) { throw Error('Not in config phase.') } }; var assertPhaseServer = function() { if (!phaseServer) { throw Error('Not in server phase.') } }; var assertPhaseServerUp = function() { if (!phaseServerUp) { throw Error('Not in serverUp phase.') } }; var pluginLoad = function(plugin) { assertPhaseInit(); if (plugin === undefined) { throw new Error('No object given'); } if (plugin.configPlugin === undefined) { throw new Error('Object is not a plugin: Missing configPlugin()'); } // run config to have key var ctx = { key: null, description: null, dependencies: [], localDir: null, localConfigTemplate: null } plugin.configPlugin(ctx); if (ctx.key === null) { throw new Error('Plugin does not provide pluginKey.'); } // Validate plugin before injection for (var objectKey in plugin) { if (objectKey.charAt(0) === '_') { continue; // tmp } if (pluginKeysAllowed.indexOf(objectKey) !== -1) { continue; } throw new Error('Illegal plugin objectKey: '+objectKey+' plugin: '+plugin.tmeta.key); } plugin['tmeta'] = ctx; plugin['troutes'] = []; plugin['tenable'] = false; if (ctx.localConfigTemplate) { var pluginJsonFile = path.join(ctx.localDir,ctx.localConfigTemplate); var pluginJsonTemplate = fs.readFileSync(pluginJsonFile, 'utf8'); plugin['configTemplateLocal'] = function (ctx) { debug('configTemplateLocal file %s',pluginJsonFile); ctx.mergeMaster(JSON.parse(pluginJsonTemplate)); }; debugPostfix = ' localConfigTemplate:\t'+pluginJsonFile; } //masterConfig.plugins[plugin.tmeta.key]=plugin; masterConfig.plugins.push(plugin); debug('pluginLoad %s',plugin.tmeta.key); }; var pluginLoadTree = function (plugins) { var keys = Object.keys(plugins); var result = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = plugins[key]; if (typeof value === 'function') { pluginLoad(new value()); } else { pluginLoadTree(value); } } }; var pluginEnable = function(pluginFilter, value) { assertPhaseInit(); if (pluginFilter === undefined) { throw new Error('No pluginKey given'); } if (value === undefined) { value = true; } var keys = Object.keys(masterConfig.plugins); for (var i = 0; i < keys.length; i++) { var pluginIdx = keys[i]; var plugin = masterConfig.plugins[pluginIdx]; var pluginKey = plugin.tmeta.key; if (pluginKey.match(pluginFilter)) { debug('pluginEnable plugin: '+pluginKey+' value: '+value); plugin.tenable = value; } } }; var pluginIndexOf = function(checkKey) { var keys = Object.keys(masterConfig.plugins); for (var i = 0; i < keys.length; i++) { var pluginKey = keys[i]; var plugin = masterConfig.plugins[pluginKey]; if (plugin.tmeta.key === checkKey) { return i; } } return -1; } var pluginOrder = function() { var keys = Object.keys(masterConfig.plugins); for (var pluginId = 0; pluginId < keys.length; pluginId++) { var pluginKey = keys[pluginId]; var plugin = masterConfig.plugins[pluginKey]; if (plugin.tmeta.dependencies.length === 0) { continue; } for (var depId in plugin.tmeta.dependencies) { var dep = plugin.tmeta.dependencies[depId]; var depIdx = pluginIndexOf(dep); if (depIdx === -1) { throw new Error('Missing plugin: '+dep+' requested by: '+plugin.tmeta.key); } if (depIdx < pluginId) { continue; } debug('pluginOrder dependency move: '+dep+' before: '+plugin.tmeta.key); var depMove = masterConfig.plugins[depIdx]; var oldPlugins = masterConfig.plugins; var newPlugins = []; newPlugins = oldPlugins.slice(0,pluginId); newPlugins.push(depMove); newPlugins = newPlugins.concat(oldPlugins.slice(pluginId,depIdx)); newPlugins = newPlugins.concat(oldPlugins.slice(depIdx+1)); masterConfig.plugins = newPlugins; return false; } } return true; } return function ConfigRegistry() { this.assertPhaseInit = assertPhaseInit; this.assertPhaseConfig = assertPhaseConfig; this.assertPhaseServer = assertPhaseServer; this.assertPhaseServerUp = assertPhaseServerUp; this.pluginCall = pluginCall; this.pluginLoad = pluginLoad; this.pluginLoadTree = pluginLoadTree; this.pluginEnable = pluginEnable; this.phaseNext = function() { if (phaseInit) { debug('phaseNext init->config'); phaseInit = false; phaseConfig = true; debug('pluginOrder start'); while (!pluginOrder()) { } debug('pluginOrder done'); var ctx = { mergeMaster: function(template) { mergeMaster(masterTemplates,ctx.tplugin.tmeta.key)(template); } }; pluginCall('configTemplate',ctx); pluginCall('configTemplateLocal',ctx); pluginCall('createBackend',{},function (plugin,backend) { masterConfig.backends[backend.getKey()]=backend; debug('createBackend: %s',backend.getKey()); }); } else if (phaseConfig) { debug('phaseNext config->server'); phaseConfig = false; phaseServer = true; tentityFillTree(masterConfig.rootTEntity); } else if (phaseServer) { debug('phaseNext server->serverUp'); phaseServer = false; phaseServerUp = true; } else if (phaseServerUp) { debug('phaseNext serverUp->done'); phaseServerUp = false; } else { throw new Error('All phases are done.'); } }; this.getRootTEntity = function() { if (masterConfig.rootTEntity === null) { masterConfig.rootTEntity = configUtil.clone(masterTemplates.masterTEntityTemplate); masterConfig.rootTEntity.tid = 'root'; debug('getRootTEntity.created'); } return masterConfig.rootTEntity; }; this.getMasterTemplates = function() { return masterTemplates; }; this.getMasterConfig = function() { return masterConfig; } this.registrateClientResource = function(clientResource, resourceType) { if (clientResource === undefined) { throw new Error('No resource provided'); } if (resourceType === undefined) { throw new Error('No resource provided'); } debug('registrateClientResource: '+clientResource+' type: '+resourceType); masterConfig.clientResources[resourceType].push(clientResource); }; this.createClientResourceFetchList = function() { var fetchList = []; for (var clientResourceIdx in masterConfig.clientResources.js) { var url = masterConfig.clientResources.js[clientResourceIdx]; fetchList.push({url:url,type:'js'}); } for (var clientResourceIdx in masterConfig.clientResources.css) { var url = masterConfig.clientResources.css[clientResourceIdx]; fetchList.push({url:url,type:'css'}); } for (var clientResourceIdx in masterConfig.clientResources.dss) { var url = masterConfig.clientResources.dss[clientResourceIdx]; fetchList.push({url:url,type:'dss'}); } return fetchList; }; }; })(); module.exports = new mod();