/**
* MOST Web Framework
* A JavaScript Web Framework
* http://themost.io
* Created by Kyriakos Barbounakis<k.barbounakis@gmail.com> on 2015-02-13.
*
* Copyright (c) 2014, Kyriakos Barbounakis k.barbounakis@gmail.com
Anthi Oikonomou anthioikonomou@gmail.com
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of MOST Web Framework nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @ignore
*/
var array = require('most-array'),
_ = require("lodash"),
dataCommon = require('./data-common'),
util = require('util'),
path = require("path"),
fs = require("fs");
/**
* @ignore
* @class
* @constructor
* @property {string} name
* @property {string} defaultUserGroup
* @property {string} unattendedExecutionAccount
* @property {number} timeout
* @property {boolean} slidingExpiration
* @property {string} loginPage
*/
function DataConfigurationAuth() {
//
}
/**
* @classdesc Holds the configuration of data modeling infrastructure
* @class
* @constructor
* @property {DataConfigurationAuth} auth
*
*/
function DataConfiguration() {
/**
* Model caching object (e.g. cfg.models.Migration, cfg.models.User etc)
* @type {*}
* @ignore
*/
this.models = {
"Migration":require("./migration.json")
};
/**
* @type {*}
* @private
*/
var dataTypes = null;
/**
* Gets or sets an array of items that indicates all the data types that is going to be used in data modeling.
* @type {*}
*/
Object.defineProperty(this, 'dataTypes', {
get: function()
{
if (dataTypes)
return dataTypes;
//get data types from configuration file
try {
dataTypes = require(path.join(process.cwd(), 'config/dataTypes.json'));
if (_.isNil(dataTypes)) {
dataCommon.log('Data: Application data types are empty. The default data types will be loaded instead.');
dataTypes = require('./dataTypes.json');
}
else {
//append default data types which are not defined in application data types
var defaultDataTypes = require('./dataTypes.json');
//enumerate default data types and replace or append application specific data types
for (var key in defaultDataTypes) {
if (dataTypes.hasOwnProperty(key)) {
if (dataTypes[key].version) {
if (dataTypes[key].version <= defaultDataTypes[key].version) {
//replace data type due to lower version
dataTypes[key] = defaultDataTypes[key];
}
}
else {
//replace data type due to invalid version
dataTypes[key] = defaultDataTypes[key];
}
}
else {
//append data type
dataTypes[key] = defaultDataTypes[key];
}
}
}
}
catch(e) {
if (e.code === 'MODULE_NOT_FOUND') {
dataCommon.log('Data: Application specific data types are missing. The default data types will be loaded instead.');
}
else {
dataCommon.log('Data: An error occured while loading application data types.');
throw e;
}
dataTypes = require('./dataTypes.json');
}
return dataTypes;
}
});
//get application adapter types, if any
var config;
try {
var env = process.env['NODE_ENV'] || 'production';
config = require(path.join(process.cwd(), 'config/app.' + env + '.json'));
}
catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
dataCommon.log('Data: The environment specific configuration cannot be found or is inaccesible.');
try {
config = require(path.join(process.cwd(), 'config/app.json'));
}
catch(e) {
if (e.code === 'MODULE_NOT_FOUND') {
dataCommon.log('Data: The default application configuration cannot be found or is inaccesible.');
}
else {
dataCommon.log('Data: An error occured while trying to open default application configuration.');
dataCommon.log(e);
}
config = { adapters:[], adapterTypes:[] };
}
}
else {
dataCommon.log('Data: An error occured while trying to open application configuration.');
dataCommon.log(e);
config = { adapters:[], adapterTypes:[] };
}
}
/**
* @type {Array}
* @private
*/
var adapters;
Object.defineProperty(this, 'adapters', {
get: function()
{
if (adapters)
return adapters;
/**
* get data types from configuration file
* @property {Array} adapters
* @type {*}
*/
adapters = config.adapters || [];
return adapters;
}
});
var adapterTypes = { };
if (config.adapterTypes) {
if (util.isArray(config.adapterTypes)) {
config.adapterTypes.forEach(function(x) {
//first of all validate module
x.invariantName = x.invariantName || 'unknown';
x.name = x.name || 'Unknown Data Adapter';
var valid = false, adapterModule;
if (x.type) {
try {
adapterModule = require(x.type);
if (typeof adapterModule.createInstance === 'function') {
valid = true;
}
else {
//adapter type does not export a createInstance(options) function
console.log(util.log("The specified data adapter type (%s) does not have the appropriate constructor. Adapter type cannot be loaded.", x.invariantName));
}
}
catch(e) {
//catch error
console.log(e);
//and log a specific error for this adapter type
console.log(util.log("The specified data adapter type (%s) cannot be instantiated. Adapter type cannot be loaded.", x.invariantName));
}
if (valid) {
//register adapter
adapterTypes[x.invariantName] = {
invariantName:x.invariantName,
name: x.name,
createInstance:adapterModule.createInstance
};
}
}
else {
console.log(util.log("The specified data adapter type (%s) does not have a type defined. Adapter type cannot be loaded.", x.invariantName));
}
});
}
}
Object.defineProperty(this, 'adapterTypes', {
get: function()
{
return adapterTypes;
}
});
var auth;
Object.defineProperty(this, 'auth', {
get: function()
{
try {
if (auth) { return auth; }
if (typeof config.settings === 'undefined' || config.settings== null) {
auth = config.auth || {};
return auth;
}
auth = config.settings.auth || {};
return auth;
}
catch(e) {
console.log('An error occured while trying to load auth configuration');
auth = {};
return auth;
}
}
});
//ensure authentication settings
config.settings = config.settings || { };
config.settings.auth = config.settings.auth || { };
this.getAuthSettings = function() {
try {
return config.settings.auth;
}
catch(e) {
var er = new Error('An error occured while trying to load auth configuration');
er.code = "ECONF";
throw er;
}
};
var path_ = path.join(process.cwd(),'config', 'models');
/**
* Gets a string which represents the path where schemas exist. The default location is the config/models folder.
* @returns {string}
*/
this.getModelPath = function() {
return path_;
};
/**
* Sets a string which represents the path where schemas exist.
* @param p
* @returns {DataConfiguration}
*/
this.setModelPath = function(p) {
path_ = p;
return this;
};
/**
* Sets a data model definition in application storage.
* Use this method in order to override default model loading process.
* @param {*} data - A generic object which represents a model definition
* @returns {DataConfiguration}
* @example
var most = require("most-data");
most.cfg.getCurrent().setModelDefinition({
"name":"UserColor",
"version":"1.1",
"title":"User Colors",
"fields":[
{ "name": "id", "title": "Id", "type": "Counter", "nullable": false, "primary": true },
{ "name": "user", "title": "User", "type": "User", "nullable": false },
{ "name": "color", "title": "Color", "type": "Text", "nullable": false, "size":12 },
{ "name": "tag", "title": "Tag", "type": "Text", "nullable": false, "size":24 }
],
"constraints":[
{"type":"unique", "fields": [ "user" ]}
],
"privileges":[
{ "mask":15, "type":"self","filter":"id eq me()" }
]
});
*/
this.setModelDefinition = function(data) {
if (_.isNil(data)) {
throw new Error("Invalid model definition. Expected object.")
}
if (typeof data === 'object') {
if (typeof data.name === 'undefined' || data.name === null) {
throw new Error("Invalid model definition. Expected model name.")
}
this.models[data.name] = data;
}
return this;
};
/**
* Gets a native object which represents the definition of the model with the given name.
* @param {string} name
* @returns {DataModel|undefined}
*/
this.getModelDefinition = function(name) {
if (_.isNil(name)) {
return;
}
if (typeof name === 'string') {
return this.model(name);
}
};
/**
* Gets a boolean which indicates whether the specified data type is defined in data types collection or not.
* @param name
* @returns {boolean}
*/
this.hasDataType = function(name) {
if (_.isNil(name)) {
return false;
}
if (typeof name !== 'string') {
return false;
}
return this.dataTypes.hasOwnProperty(name);
}
}
/**
* @returns {*}
* @param name {string}
*/
DataConfiguration.prototype.model = function(name)
{
var self = this, i;
if (typeof name !== 'string')
return null;
//first of all try to find if model definition is already in cache
if (typeof this.models[name] !== 'undefined')
//and return it
return this.models[name];
//otherwise try to find model with case insensitivity
var keys = Object.keys(this.models), mr = new RegExp('^' + name + '$','i');
for (i = 0; i < keys.length; i++) {
mr.lastIndex=0;
if (mr.test(keys[i]))
return this.models[keys[i]];
}
//otherwise open definition file
var modelPath = this.getModelPath();
if (!fs.existsSync(modelPath)) {
//models folder does not exist
//so set model to null
this.models[name]=null;
//and return
return null;
}
//read files from models directory
var files;
//store file list in a private variable
if (typeof this._files === 'undefined') { this._files = fs.readdirSync(modelPath); }
//and finally get this list of file
files = this._files;
if (files.length==0)
return null;
var r = new RegExp('^' + name.concat('.json') + '$','i');
for (i = 0; i < files.length; i++) {
r.lastIndex=0;
if (r.test(files[i])) {
//build model file path
var finalPath = path.join(modelPath, files[i]);
//get model
var result = require(finalPath), finalName = result.name;
//cache model definition
self.models[finalName] = result;
//and finally return this definition
return result;
}
}
return null;
};
/**
* @private
*/
var namedConfiguations_ = { };
/**
* @exports most-data/data-configuration
*/
var cfg = {
};
/**
* @type DataConfiguration
* @private
*/
var cfg_;
Object.defineProperty(cfg, 'current', {
get: function() {
if (cfg_)
return cfg_;
cfg_ = new DataConfiguration();
return cfg_;
}, configurable:false, enumerable:false
});
/**
* Gets the current data configuration
* @returns DataConfiguration - An instance of DataConfiguration class which represents the current data configuration
*/
cfg.getCurrent = function() {
return this.current;
};
/**
* Creates an instance of DataConfiguration class
* @returns {DataConfiguration} - Returns an instance of DataConfiguration class
*/
cfg.createInstance= function() {
return new DataConfiguration();
};
/**
* Gets an instance of DataConfiguration class based on the given name.
* If the named data configuration does not exists, it will create a new instance of DataConfiguration class with the given name.
* @param {string} name - A string which represents the name of the data configuration
* @returns {DataConfiguration}
*/
cfg.getNamedConfiguration = function(name) {
if (typeof name !== 'string') {
throw new Error("Invalid configuration name. Expected string.");
}
if (name.length == 0) {
throw new Error("Invalid argument. Configuration name may not be empty string.");
}
if (/^current$/i.test(name)) {
return cfg.current;
}
if (typeof namedConfiguations_[name] !== 'undefined')
return namedConfiguations_[name];
namedConfiguations_[name] = new DataConfiguration();
return namedConfiguations_[name];
};
cfg.DataConfiguration = DataConfiguration;
module.exports = cfg;