define(['mmirf/resources', 'mmirf/logger', 'mmirf/util/loadFile', 'mmirf/util/isArray', 'module'],
/**
* A class for managing the configuration. <br>
* It's purpose is to load the configuration and settings automatically.
*
* This "class" is structured as a singleton - so that only one instance is in use.<br>
*
* @name ConfigurationManager
* @memberOf mmir
* @static
* @class
*
* @requires mmir.require for getting/setting language code (e.g. see {@link mmir.ConfigurationManager.getLanguage}
*
*/
function (
res, Logger, loadFile, isArray, module
){
//the next comment enables JSDoc2 to map all functions etc. to the correct class description
/** @scope ConfigurationManager.prototype */
/**
* Object containing the instance of the class {@link mmir.ConfigurationManager}.
*
* @type Object
* @private
*
* @memberOf ConfigurationManager#
*/
var instance = null;
/**
* @private
* @type Logger
* @memberOf ConfigurationManager#
*/
var logger = Logger.create(module);
/**
* @private
* @type ConfigurationManagerModuleConfig
* @memberOf ConfigurationManager#
*/
var _conf = module.config(module);
/**
* Constructor-Method of Singleton mmir.ConfigurationManager.
*
* @private
*
* @memberOf ConfigurationManager#
*/
function constructor(){
/** @scope ConfigurationManager.prototype */
/**
* the configuration data (i.e. properties)
* @type Object
* @private
*
* @memberOf ConfigurationManager#
*/
var configData = null;
// /**
// * Reference to the {@link mmir.LanguageManager} instance.
// *
// * Will be initialized lazily
// *
// * @type LanguageManager
// * @private
// *
// * @see #getLanguage
// * @see #setLanguage
// *
// * @memberOf LanguageManager#
// */
// var languageManager = null;
// /**
// * HELPER returns (and sets if necessary) {@link #languageManager}
// *
// * @returns {mmir.LanguageManager} the LanguageManager instance
// * @private
// *
// * @memberOf LanguageManager#
// */
// var getLanguageManager = function(){
// if(!languageManager){
// var req;
// if(typeof mmir === 'undefined'){
// //fallback if global mmir is undefined, try to use global require-function
// req = require;
// } else {
// req = mmir.require;
// }
// languageManager = req('mmirf/languageManager');
// }
// return languageManager;
// };
/**
* Helper that loads configuration file synchronously.
*
* @private
* @memberOf ConfigurationManager#
*/
//FIXME change implementation to async-loading?
// -> would need to add init()-function, with callback and/or return Deferred
function _loadConfigFile(){
if(_conf && _conf.configuration){
logger.verbose("loadConfigFile(): loaded configuration from module.config().configuration");
configData = _conf.configuration;
return;/////////// EARLY EXIT ///////////////
}
loadFile({
async: false,
dataType: "json",
url: res.getConfigurationFileUrl(),
success: function(data){
logger.verbose("loadConfigFile(): loaded configuration from "+res.getConfigurationFileUrl());
if(data){
configData = data;
}
},
error: function(data){
var errStr = "loadConfigFile(): failed to load configuration from '"+res.getConfigurationFileUrl()+"'! ERROR: ";
try{
errStr += JSON.stringify(data);
logger.error(errStr);
}catch(e){
logger.error(errStr, errStr);
}
}
});
}
//immediately load the configuration:
_loadConfigFile();
/**
* "Normalizes" a string or an array into a path:
* the result is a single, flat array where each string has
* been separated at dots (i.e. each path component is a separate entry).
*
* @example
* //result is ['dot', 'separated', 'list']
* _getAsPath('dot.separated.list');
* _getAsPath(['dot', 'separated.list']);
* _getAsPath(['dot', 'separated', 'list']);
*
* @private
* @param {String|Array<String>} propertyName
* resolves a dot-separated property-name into an array.
* If <code>propertyName</code> is an Array, all contained
* String entries will be resolved, if necessary
*
* @memberOf ConfigurationManager#
*/
function _getAsPath(propertyName){
var path = propertyName;
if( ! isArray(path)){
path = propertyName.split('.');
}
else {
path = _toPathArray(propertyName);
}
return path;
}
/**
* "Normalizes" an array of Strings by separating
* each String at dots and creating one single (flat) array where
* each path-component is a single entry.
*
* @private
* @param {Array<String>} pathList
* resolves an array with paths, i.e. dot-separated property-names
* into a single, flat array where each path component is a separate Strings
*
* @memberOf ConfigurationManager#
*/
function _toPathArray(pathList){
var entry;
var increase = 0;
var size = pathList.length;
var tempPath;
for(var i=0; i < size; ++i){
entry = pathList[i];
tempPath = entry.split('.');
//if entry contained dot-separated path:
// replace original entry with the new sub-path
if(tempPath.length > 1){
pathList[i] = tempPath;
increase += (tempPath.length - 1);
}
}
//if sup-paths were inserted: flatten the array
if(increase > 0){
//create new array that can hold all entries
var newPath = new Array(size + increase);
var index = 0;
for(var i=0; i < size; ++i){
entry = pathList[i];
//flatten sub-paths into the new array:
if( isArray(entry) ){
for(var j=0, len=entry.length; j < len; ++j){
newPath[index++] = entry[j];
}
}
else {
//for normal entries: just insert
newPath[index++] = entry;
}
}
pathList = newPath;
}
return pathList;
}
/** @lends mmir.ConfigurationManager.prototype */
return {
// public members
/**
* Returns the value of a property.
*
* @function
* @param {String} propertyName
* The name of the property.
* NOTE: If the property does not exists at the root-level,
* dot-separated names will be resolved into
* object-structures, e.g.
* <code>some.property</code> will be resolved
* so that the <code>value</code> at:
* <code>some: {property: <value>}</code>
* will be returned
* @param {any} [defaultValue] OPTIONAL
* a default value that will be returned, in case there is no property
* <code>propertyName</code>.
* @param {Boolean} [useSafeAccess] OPTIONAL
* if <code>true</code>, resolution of dot-separated paths
* will be done "safely", i.e. if a path-element does not
* exists, no <code>error</code> will be thrown, but instead
* the function will return the <code>defaultValue</code>
* (which will be <code>undefined</code> if the argument is not given).
*
* <br>DEFAULT: <code>true</code>
* <br>NOTE: if this argument is used, param <code>defaultValue</code> must also be given!
*
* @returns {any}
* The value of the property
* @public
* @memberOf mmir.ConfigurationManager.prototype
*/
get: function(propertyName, defaultValue, useSafeAccess){
if(configData){
if(typeof configData[propertyName] !== 'undefined'){
return configData[propertyName];//////////// EARLY EXIT ///////////////////
}
var path = _getAsPath(propertyName);
//ASSERT path.length == 1: already handled by if(configData[propertyName]...
if(path.length > 1){
if(typeof useSafeAccess === 'undefined'){
useSafeAccess = true;
}
if(useSafeAccess && typeof configData[ path[0] ] === 'undefined'){
return defaultValue;///////////// EARLY EXIT /////////////////////////
}
var obj = configData;
var prop;
while(path.length > 1){
prop = path.shift();
obj = obj[prop];
if(useSafeAccess && typeof obj === 'undefined'){
return defaultValue;///////////// EARLY EXIT /////////////////////
}
}
//ASSERT now: path.length === 1
if(typeof obj[path[0]] === 'undefined'){
return defaultValue;///////////// EARLY EXIT /////////////////////
}
return obj[path[0]];
}
}
return defaultValue;
},
/**
* Sets a property to a given value.
*
* @function
* @param {String|Array<String>} propertyName
*
* The name of the property.
*
* If <code>propertyName</code> is an Array, it
* will be treated as if its entries were path-elements
* analogous to a dot-separated String propertyName.
*
* NOTE: dot-separated names will be resolved into
* object-structures, e.g.
* <code>some.property</code> will be resolved
* so that the <code>value</code> will set to:
* <code>some: {property: <value>}</code>
*
* @param {any} value
* The value of the property
*
* @throws {Error} if the propertyName is dot-separated AND
* one of its path-elements (except for the last)
* already exists AND its type is not 'object'
*
* @public
* @memberOf mmir.ConfigurationManager.prototype
*/
set: function(propertyName, value){
if(!configData){
configData = {};
}
var path = _getAsPath(propertyName);
if(path.length > 1){
var obj = configData;
var prop;
while(path.length > 1){
prop = path.shift();
if(typeof obj[prop] === 'undefined' || obj[prop] === null){
obj[prop] = {};
}
else if(typeof obj[prop] !== 'object'){
throw new Error('Cannot expand path "'+propertyName+'": path-element "'+prop+'" already exist and has type "'+(typeof obj[prop])+'"');
}
obj = obj[prop];
}
//ASSERT path.length == 1
obj[path[0]] = value;
}
else {
configData[propertyName] = value;
}
},
/**
* Uses {@link #get}.
*
* If the propertyName does not exists, returns <code>undefined</code>,
* otherwise values will be converted into Boolean values.
*
* Special case for Strings:
* the String <code>"false"</code> will be converted to
* Boolean value <code>false</code>.
*
* @public
* @param {any} [defaultValue] OPTIONAL
*
* if a default value is specified and there exists
* no property <code>propertyName</code>, the
* specified default value will be returned.
*
* NOTE: if this argument is used, <code>useSafeAccess</code> must also be given!
*
* NOTE: the default value will also be converted
* to a Boolean value, if necessary.
*
* @see {@link #get}
* @memberOf mmir.ConfigurationManager.prototype
*/
getBoolean: function(propertyName, defaultValue, useSafeAccess){
var val = this.get(propertyName, defaultValue, useSafeAccess);
if(typeof val !== 'undefined'){
if( val === 'false'){
return false;
}
else {
return val? true : false;
}
}
},
/**
* Uses {@link #get}.
*
* If the property does not exists, returns <code>undefined</code>,
* otherwise values will be converted into String values.
*
* If the value has not the type <code>"string"</code>, it will
* be converted by <code>JSON.stringify</code>.
*
* @public
* @param {any} [defaultValue] OPTIONAL
* if a default value is specified and there exists
* no property <code>propertyName</code>, the
* specified default value will be returned.
*
* NOTE: if this argument is used, <code>useSafeAccess</code> must also be given!
*
* NOTE: the default value will also be converted
* to a String value, if necessary.
*
* @see {@link #get}
* @memberOf mmir.ConfigurationManager.prototype
*/
getString: function(propertyName, defaultValue, useSafeAccess){
var val = this.get(propertyName, defaultValue, useSafeAccess);
if(typeof val !== 'undefined'){
if(typeof val === 'string'){
return val;
}
else {
return JSON.stringify(val);
}
}
}
};//END: return {...
}//END: constructor = function(){...
instance = new constructor();
return instance;
});