cloudcms-server
Version:
Cloud CMS Application Server Module
692 lines (568 loc) • 21.6 kB
JavaScript
var path = require("path");
var async = require("async");
var util = require("../../util/util");
// debug helper method to reduce logging
var _log = function(text)
{
//process.log(text);
};
/**
* Provides an adapter around a store.
*
* @param configStore
*/
module.exports = function(configStore)
{
/**
* Reduces a path like:
*
* /a/b/c/d/../e/../../f
*
* to
*
* /a/b/f
*
* @param p
*/
var normalizePath = function(p)
{
return path.normalize(p);
};
var generateSubscriberKey = function() {
var count = 0;
return function(pageKey, region, order)
{
return "gadget" + (count++);
}
}();
var loadConfigObject = function(path, callback)
{
configStore.existsFile(path, function(exists) {
if (!exists)
{
return callback({
"message": "Unable to find config file: " + path
});
}
configStore.readFile(path, function(err, data) {
if (err)
{
return callback(err);
}
try
{
var object = util.jsonParse("" + data);
callback(null, object);
}
catch (err)
{
process.log("An error occurred while parsing JSON: " + data + " for file: " + path);
process.log(err);
callback(err);
}
});
});
};
var loadPages = function(dirPath, pageKey, context, callback)
{
configStore.existsFile(dirPath, function(exists) {
if (!exists)
{
return callback();
}
// collect all of the page configs under this path
var configs = {};
configStore.listFiles(dirPath, function(err, filenames) {
if (err) {
process.log("Caught error in loadPages listFiles() - " + JSON.stringify(err));
return callback(err);
}
if (!filenames || filenames.length === 0) {
return callback();
}
var fns = [];
for (var i = 0; i < filenames.length; i++)
{
var fn = function(dirPath, filename, configs) {
return function(done) {
var childPath = path.join(dirPath, filename);
if (filename.endsWith(".json"))
{
loadConfigObject(childPath, function(err, config) {
if (config) {
configs[filename] = config;
}
done();
});
}
else
{
configStore.fileStats(childPath, function(err, stats) {
if (err)
{
done(err);
return;
}
if (stats && stats.directory)
{
// iterate down
loadPages(childPath, path.join(pageKey, filename), context, function(err) {
done(err);
});
}
else {
done();
}
});
}
}
}(dirPath, filenames[i], configs);
fns.push(fn);
}
async.series(fns, function(errors) {
// if we found a "page.json" at this level...
if (configs["page.json"])
{
// populate page into context
// if it already exists, merge
var d = context.pages[pageKey];
if (!d) {
d = {};
}
util.merge(configs["page.json"], d);
context.pages[pageKey] = d;
delete configs["page.json"];
_log(" -> Registered page: " + pageKey);
// everything else in the directory is assumed to be a region binding
for (var regionFileName in configs)
{
var region = regionFileName.substring(0, regionFileName.indexOf(".json"));
var gadget = configs[regionFileName];
// generate a unique gadget key
var gadgetKey = generateSubscriberKey(pageKey, region, 0);
gadget["key"] = gadgetKey;
if (!context.pages[pageKey].bindings) {
context.pages[pageKey].bindings = {};
}
context.pages[pageKey].bindings[region] = gadget;
context.gadgets[gadgetKey] = gadget;
_log(" -> Registered gadget: " + gadgetKey + " (" + region + ")");
}
}
callback();
});
});
});
};
var loadBlocks = function(dirPath, context, callback)
{
configStore.existsFile(dirPath, function(exists) {
if (!exists)
{
return callback({
"message": "Unable to find directory: " + dirPath
});
}
// collect all of the page configs under this path
var configs = {};
configStore.listFiles(dirPath, function(err, filenames) {
if (err) {
process.log("Caught error in loadBlocks listFiles() - " + JSON.stringify(err));
return callback(err);
}
if (!filenames || filenames.length === 0) {
return callback();
}
var fns = [];
for (var i = 0; i < filenames.length; i++)
{
var fn = function(dirPath, filename)
{
return function(done)
{
var childPath = path.join(dirPath, filename);
if (filename.endsWith(".json"))
{
loadConfigObject(childPath, function(err, config) {
if (config) {
configs[childPath] = config;
}
done();
});
}
else
{
configStore.fileStats(childPath, function(err, stats) {
if (stats && stats.directory) {
loadBlocks(childPath, context, function(err) {
done(err);
});
}
else
{
// it is something else, something we do not care about (such as .DS_Store)
done();
}
});
}
}
}(dirPath, filenames[i]);
fns.push(fn);
}
async.series(fns, function(errors) {
// merge in any configs we found at this level
for (var k in configs) {
context.blocks[k] = configs[k];
}
callback();
});
});
});
};
/**
* Loads page and gadget binding information.
*
* @return {Object}
*/
var x = {};
x.init = function(callback)
{
// watch and load from directoryPath...
// /config/application.js
// /config/<module>/pages/**.json
// /config/<module>/blocks/**.json
// -> within each directory, load (region).json for gadget definitions
var loadContext = function(callback)
{
var context = {};
loadConfigObject("application.json", function(err, app) {
if (app) {
context.application = app;
}
context.pages = {};
context.gadgets = {};
context.blocks = {};
// find all modules
configStore.listFiles("/", function (err, filenames) {
if (err) {
process.log("Caught error in loadContext listFiles() - " + JSON.stringify(err));
return callback(err);
}
if (!filenames || filenames.length === 0) {
return callback(null, context);
}
var fns = [];
for (var i = 0; i < filenames.length; i++) {
var fn = function (filename, context) {
return function (done) {
// NOTE: filename = module name
configStore.fileStats(filename, function (err, stats) {
if (stats.directory) {
loadPages(path.join(filename, "pages"), "", context, function (err) {
if (err) {
done(err);
return;
}
loadBlocks(path.join(filename, "blocks"), context, function (err) {
done(err);
});
});
}
else {
done();
}
});
}
}(filenames[i], context);
fns.push(fn);
}
async.series(fns, function (errors) {
callback(null, context);
});
});
});
};
loadContext(function(err, context) {
if (err)
{
return callback(err);
}
// wrap as registry
var registry = compileContextToRegistry(context);
// all done
callback(null, registry);
});
};
var compilePage = function(context, pageKey)
{
var obj = {};
var pageObj = context.pages[pageKey];
if (!pageObj)
{
process.log("Missing page object for key: " + pageKey);
return;
}
if (pageObj["extends"])
{
var ext = pageObj["extends"]; // ../_platform-manage
var p1 = pageKey;
var p2 = null;
if (ext.indexOf("./") > -1)
{
// append so that we have a path like "/a/b/../../c/d" -> "/c/d"
pageKey = pageKey + "/" + ext;
// now normalize the path
ext = normalizePath(pageKey);
p2 = ext;
}
if (typeof(ext) === "string")
{
var parentObj = compilePage(context, ext);
if (!parentObj)
{
process.log("Page Key 1: " + p1);
process.log("Page Key 2: " + p2);
process.log("WARNING: Failed to find parent page: " + ext + " for pageKey: " + p1);
}
else
{
// strip uris
delete parentObj.uri;
util.merge(parentObj, obj);
}
}
else
{
for (var z = 0; z < ext.length; z++)
{
var parentObj = compilePage(context, ext[z]);
if (!parentObj)
{
console.error("WARNING: Failed to find parent page: " + ext + " for pageKey: " + pageKey);
}
else
{
delete parentObj.uri;
util.merge(parentObj, obj);
}
}
}
}
// copy ourselves in
util.merge(pageObj, obj);
// remove special stuff from the resulting obj
delete obj["extends"];
return obj;
};
var compileContextToRegistry = function(context)
{
_log("Configuration Store");
// compile the pages
//_log(" -> Compiling pages");
context.compiledPages = {};
for (var pageKey in context.pages)
{
var page = context.pages[pageKey];
if (page.uri) {
context.compiledPages[pageKey] = compilePage(context, pageKey);
}
}
//_log(" -> Page compilation completed");
// store page count
context.pageCount = 0;
for (var pageKey in context.pages)
{
context.pageCount++;
}
//_log(" -> Page count: " + context.pageCount);
// compiled page count
context.compiledPageCount = 0;
for (var pageKey in context.compiledPages)
{
context.compiledPageCount++;
}
//_log(" -> Compiled page count: " + context.compiledPageCount);
// gadget bindings count
context.gadgetCount = 0;
for (var gadgetKey in context.gadgets)
{
context.gadgetCount++;
}
//_log(" -> Gadget count: " + context.gadgetCount);
// blocks count
context.blockCount = 0;
for (var blockKey in context.blocks)
{
context.blockCount++;
}
//_log(" -> Block count: " + context.blockCount);
///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////
var createPageConfig = function(pageKey, page, callback)
{
var self = this;
var config = {};
// application
config.application = context.application;
// page
config.pageKey = pageKey;
config.page = page;
config.page.key = pageKey;
// compiled page
//config.compiledPage = context.compiledPages[pageKey];
//_log("Compiled page is -> " + JSON.stringify(config.compiledPage));
// template
config.template = config.page.template;
var doBind = function(config, regionName, gadget)
{
var gadgetCopy = util.clone(gadget, true);
gadgetCopy.region = regionName;
config.gadgets[gadget.key] = gadgetCopy;
};
// bindings and gadget configurations
config.gadgets = {};
if (config.page.bindings)
{
for (var regionName in config.page.bindings)
{
var gadgetObjectOrArray = config.page.bindings[regionName];
if (gadgetObjectOrArray.push)
{
for (var i = 0; i < gadgetObjectOrArray.length; i++)
{
doBind(config, regionName, gadgetObjectOrArray[i]);
}
}
else
{
doBind(config, regionName, gadgetObjectOrArray);
}
}
}
callback.call(self, config);
};
var registry = {};
/**
* Loads a compiled page configuration for a given uri.
*
* @param uriOrKey
* @param callback
*/
registry.loadPage = function(uriOrKey, callback) {
var self = this;
var found = null;
var foundPageKey = null;
for (var pageKey in context.compiledPages)
{
var page = context.compiledPages[pageKey];
if (page.uri)
{
// check for uri match
// page uris can have wildcards or {value} tokens in them
// we convert the page uri to a regex
var regex = page.uri;
var i = 0;
var j = -1;
var k = -1;
while (i < regex.length)
{
if (regex[i] == "{")
{
j = i;
}
if (regex[i] == "}")
{
k = i;
regex = regex.substring(0, j) + "(.*)" + regex.substring(k+1);
j = -1;
k = -1;
i = 0;
}
i++;
}
regex += "$";
var pattern = new RegExp(regex);
var matches = uriOrKey.match(pattern);
if (pageKey == uriOrKey || (matches && matches.length > 0))
{
found = page;
foundPageKey = pageKey;
break;
}
}
}
// if no page, the error out?
if (!found)
{
process.log("No page found for uriOrKey: " + uriOrKey);
}
createPageConfig.call(self, foundPageKey, found, function(config) {
callback.call(self, config);
});
};
/**
* Loads all compiled page configurations
*
* @param callback
*/
registry.loadPages =function(callback) {
var self = this;
var configs = {};
// if no pages then just bail
var pageCount = context.compiledPageCount;
if (pageCount == 0)
{
callback.call(self, configs);
return;
}
for (var pageKey in context.compiledPages)
{
createPageConfig.call(self, pageKey, context.compiledPages[pageKey], function(config) {
configs[pageKey] = config;
pageCount--;
if (pageCount == 0)
{
callback.call(self, configs);
}
});
}
};
registry.loadBlocks = function(callback) {
var self = this;
callback.call(self, context.blocks);
};
registry.loadGadget = function(pageKey, region, order, callback) {
var binding = context.page.bindings[region];
if (!binding)
{
process.log("Unable to find gadget, page: " + pageKey + ", region: " + region + ", order: " + order);
}
var gadget = null;
if (binding.push)
{
gadget = binding[order];
}
else
{
gadget = binding;
}
callback.call(this, gadget);
};
registry.loadApplication = function(callback) {
callback.call(this, context.application);
};
registry.reloadContext = function(newContext) {
for (var key in newContext)
{
context[key] = newContext[key];
}
};
registry.getConfigStore = function()
{
return configStore;
};
return registry;
};
return x;
};