Source: manager/settings/configurationManager.js

  1. 
  2. define(['mmirf/constants', 'mmirf/util/loadFile', 'mmirf/util/isArray'],
  3. /**
  4. * A class for managing the configuration. <br>
  5. * It's purpose is to load the configuration and settings automatically.
  6. *
  7. * This "class" is structured as a singleton - so that only one instance is in use.<br>
  8. *
  9. * @name ConfigurationManager
  10. * @memberOf mmir
  11. * @static
  12. * @class
  13. *
  14. * @requires mmir.require for getting/setting language code (e.g. see {@link mmir.ConfigurationManager.getLanguage}
  15. *
  16. */
  17. function (
  18. constants, loadFile, isArray
  19. ){
  20. //the next comment enables JSDoc2 to map all functions etc. to the correct class description
  21. /** @scope ConfigurationManager.prototype */
  22. /**
  23. * Object containing the instance of the class {@link mmir.ConfigurationManager}.
  24. *
  25. * @type Object
  26. * @private
  27. *
  28. * @memberOf ConfigurationManager#
  29. */
  30. var instance = null;
  31. /**
  32. * Constructor-Method of Singleton mmir.ConfigurationManager.
  33. *
  34. * @private
  35. *
  36. * @memberOf ConfigurationManager#
  37. */
  38. function constructor(){
  39. /** @scope ConfigurationManager.prototype */
  40. /**
  41. * the configuration data (i.e. properties)
  42. * @type Object
  43. * @private
  44. *
  45. * @memberOf ConfigurationManager#
  46. */
  47. var configData = null;
  48. // /**
  49. // * Reference to the {@link mmir.LanguageManager} instance.
  50. // *
  51. // * Will be initialized lazily
  52. // *
  53. // * @type LanguageManager
  54. // * @private
  55. // *
  56. // * @see #getLanguage
  57. // * @see #setLanguage
  58. // *
  59. // * @memberOf LanguageManager#
  60. // */
  61. // var languageManager = null;
  62. // /**
  63. // * HELPER returns (and sets if necessary) {@link #languageManager}
  64. // *
  65. // * @returns {mmir.LanguageManager} the LanguageManager instance
  66. // * @private
  67. // *
  68. // * @memberOf LanguageManager#
  69. // */
  70. // var getLanguageManager = function(){
  71. // if(!languageManager){
  72. // var req;
  73. // if(typeof mmir === 'undefined'){
  74. // //fallback if global mmir is undefined, try to use global require-function
  75. // req = require;
  76. // } else {
  77. // req = mmir.require;
  78. // }
  79. // languageManager = req('mmirf/languageManager');
  80. // }
  81. // return languageManager;
  82. // };
  83. /**
  84. * Helper that loads configuration file synchronously.
  85. *
  86. * @private
  87. * @memberOf ConfigurationManager#
  88. */
  89. //FIXME change implementation to async-loading?
  90. // -> would need to add init()-function, with callback and/or return Deferred
  91. function _loadConfigFile(){
  92. loadFile({
  93. async: false,
  94. dataType: "json",
  95. url: constants.getConfigurationFileUrl(),
  96. success: function(data){
  97. // console.debug("ConfigurationManager.constructor: loaded language settings from "+constants.getConfigurationFileUrl());//debug
  98. if(data){
  99. configData = data;
  100. }
  101. },
  102. error: function(data){
  103. var errStr = "ConfigurationManager.constructor: failed to load configuration from '"+constants.getConfigurationFileUrl()+"'! ERROR: ";
  104. try{
  105. errStr += JSON.stringify(data);
  106. console.error(errStr);
  107. }catch(e){
  108. console.error(errStr, errStr);
  109. }
  110. }
  111. });
  112. }
  113. //immediately load the configuration:
  114. _loadConfigFile();
  115. /**
  116. * "Normalizes" a string or an array into a path:
  117. * the result is a single, flat array where each string has
  118. * been separated at dots (i.e. each path component is a separate entry).
  119. *
  120. * @example
  121. * //result is ['dot', 'separated', 'list']
  122. * _getAsPath('dot.separated.list');
  123. * _getAsPath(['dot', 'separated.list']);
  124. * _getAsPath(['dot', 'separated', 'list']);
  125. *
  126. * @private
  127. * @param {String|Array<String>} propertyName
  128. * resolves a dot-separated property-name into an array.
  129. * If <code>propertyName</code> is an Array, all contained
  130. * String entries will be resolved, if necessary
  131. *
  132. * @memberOf ConfigurationManager#
  133. */
  134. function _getAsPath(propertyName){
  135. var path = propertyName;
  136. if( ! isArray(path)){
  137. path = propertyName.split('.');
  138. }
  139. else {
  140. path = _toPathArray(propertyName);
  141. }
  142. return path;
  143. }
  144. /**
  145. * "Normalizes" an array of Strings by separating
  146. * each String at dots and creating one single (flat) array where
  147. * each path-component is a single entry.
  148. *
  149. * @private
  150. * @param {Array<String>} pathList
  151. * resolves an array with paths, i.e. dot-separated property-names
  152. * into a single, flat array where each path component is a separate Strings
  153. *
  154. * @memberOf ConfigurationManager#
  155. */
  156. function _toPathArray(pathList){
  157. var entry;
  158. var increase = 0;
  159. var size = pathList.length;
  160. var tempPath;
  161. for(var i=0; i < size; ++i){
  162. entry = pathList[i];
  163. tempPath = entry.split('.');
  164. //if entry contained dot-separated path:
  165. // replace original entry with the new sub-path
  166. if(tempPath.length > 1){
  167. pathList[i] = tempPath;
  168. increase += (tempPath.length - 1);
  169. }
  170. }
  171. //if sup-paths were inserted: flatten the array
  172. if(increase > 0){
  173. //create new array that can hold all entries
  174. var newPath = new Array(size + increase);
  175. var index = 0;
  176. for(var i=0; i < size; ++i){
  177. entry = pathList[i];
  178. //flatten sub-paths into the new array:
  179. if( isArray(entry) ){
  180. for(var j=0, len=entry.length; j < len; ++j){
  181. newPath[index++] = entry[j];
  182. }
  183. }
  184. else {
  185. //for normal entries: just insert
  186. newPath[index++] = entry;
  187. }
  188. }
  189. pathList = newPath;
  190. }
  191. return pathList;
  192. }
  193. /** @lends mmir.ConfigurationManager.prototype */
  194. return {
  195. // public members
  196. /**
  197. * Returns the value of a property.
  198. *
  199. * @function
  200. * @param {String} propertyName
  201. * The name of the property.
  202. * NOTE: If the property does not exists at the root-level,
  203. * dot-separated names will be resolved into
  204. * object-structures, e.g.
  205. * <code>some.property</code> will be resolved
  206. * so that the <code>value</code> at:
  207. * <code>some: {property: &lt;value&gt;}</code>
  208. * will be returned
  209. * @param {any} [defaultValue] OPTIONAL
  210. * a default value that will be returned, in case there is no property
  211. * <code>propertyName</code>.
  212. * @param {Boolean} [useSafeAccess] OPTIONAL
  213. * if <code>true</code>, resolution of dot-separated paths
  214. * will be done "safely", i.e. if a path-element does not
  215. * exists, no <code>error</code> will be thrown, but instead
  216. * the function will return the <code>defaultValue</code>
  217. * (which will be <code>undefined</code> if the argument is not given).
  218. *
  219. * <br>DEFAULT: <code>true</code>
  220. * <br>NOTE: if this argument is used, param <code>defaultValue</code> must also be given!
  221. *
  222. * @returns {any}
  223. * The value of the property
  224. * @public
  225. * @memberOf mmir.ConfigurationManager.prototype
  226. */
  227. get: function(propertyName, defaultValue, useSafeAccess){
  228. if(configData){
  229. if(typeof configData[propertyName] !== 'undefined'){
  230. return configData[propertyName];//////////// EARLY EXIT ///////////////////
  231. }
  232. var path = _getAsPath(propertyName);
  233. //ASSERT path.length == 1: already handled by if(configData[propertyName]...
  234. if(path.length > 1){
  235. if(typeof useSafeAccess === 'undefined'){
  236. useSafeAccess = true;
  237. }
  238. if(useSafeAccess && typeof configData[ path[0] ] === 'undefined'){
  239. return defaultValue;///////////// EARLY EXIT /////////////////////////
  240. }
  241. var obj = configData;
  242. var prop;
  243. while(path.length > 1){
  244. prop = path.shift();
  245. obj = obj[prop];
  246. if(useSafeAccess && typeof obj === 'undefined'){
  247. return defaultValue;///////////// EARLY EXIT /////////////////////
  248. }
  249. }
  250. //ASSERT now: path.length === 1
  251. if(typeof obj[path[0]] === 'undefined'){
  252. return defaultValue;///////////// EARLY EXIT /////////////////////
  253. }
  254. return obj[path[0]];
  255. }
  256. }
  257. return defaultValue;
  258. },
  259. /**
  260. * Sets a property to a given value.
  261. *
  262. * @function
  263. * @param {String|Array<String>} propertyName
  264. *
  265. * The name of the property.
  266. *
  267. * If <code>propertyName</code> is an Array, it
  268. * will be treated as if its entries were path-elements
  269. * analogous to a dot-separated String propertyName.
  270. *
  271. * NOTE: dot-separated names will be resolved into
  272. * object-structures, e.g.
  273. * <code>some.property</code> will be resolved
  274. * so that the <code>value</code> will set to:
  275. * <code>some: {property: &lt;value&gt;}</code>
  276. *
  277. * @param {any} value
  278. * The value of the property
  279. *
  280. * @throws {Error} if the propertyName is dot-separated AND
  281. * one of its path-elements (except for the last)
  282. * already exists AND its type is not 'object'
  283. *
  284. * @public
  285. * @memberOf mmir.ConfigurationManager.prototype
  286. */
  287. set: function(propertyName, value){
  288. if(!configData){
  289. configData = {};
  290. }
  291. var path = _getAsPath(propertyName);
  292. if(path.length > 1){
  293. var obj = configData;
  294. var prop;
  295. while(path.length > 1){
  296. prop = path.shift();
  297. if(typeof obj[prop] === 'undefined' || obj[prop] === null){
  298. obj[prop] = {};
  299. }
  300. else if(typeof obj[prop] !== 'object'){
  301. throw new Error('Cannot expand path "'+propertyName+'": path-element "'+prop+'" already exist and has type "'+(typeof obj[prop])+'"');
  302. }
  303. obj = obj[prop];
  304. }
  305. //ASSERT path.length == 1
  306. obj[path[0]] = value;
  307. }
  308. else {
  309. configData[propertyName] = value;
  310. }
  311. },
  312. /**
  313. * Uses {@link #get}.
  314. *
  315. * If the propertyName does not exists, returns <code>undefined</code>,
  316. * otherwise values will be converted into Boolean values.
  317. *
  318. * Special case for Strings:
  319. * the String <code>"false"</code> will be converted to
  320. * Boolean value <code>false</code>.
  321. *
  322. * @public
  323. * @param {any} [defaultValue] OPTIONAL
  324. *
  325. * if a default value is specified and there exists
  326. * no property <code>propertyName</code>, the
  327. * specified default value will be returned.
  328. *
  329. * NOTE: if this argument is used, <code>useSafeAccess</code> must also be given!
  330. *
  331. * NOTE: the default value will also be converted
  332. * to a Boolean value, if necessary.
  333. *
  334. * @see {@link #get}
  335. * @memberOf mmir.ConfigurationManager.prototype
  336. */
  337. getBoolean: function(propertyName, defaultValue, useSafeAccess){
  338. var val = this.get(propertyName, defaultValue, useSafeAccess);
  339. if(typeof val !== 'undefined'){
  340. if( val === 'false'){
  341. return false;
  342. }
  343. else {
  344. return val? true : false;
  345. }
  346. }
  347. },
  348. /**
  349. * Uses {@link #get}.
  350. *
  351. * If the property does not exists, returns <code>undefined</code>,
  352. * otherwise values will be converted into String values.
  353. *
  354. * If the value has not the type <code>"string"</code>, it will
  355. * be converted by <code>JSON.stringify</code>.
  356. *
  357. * @public
  358. * @param {any} [defaultValue] OPTIONAL
  359. * if a default value is specified and there exists
  360. * no property <code>propertyName</code>, the
  361. * specified default value will be returned.
  362. *
  363. * NOTE: if this argument is used, <code>useSafeAccess</code> must also be given!
  364. *
  365. * NOTE: the default value will also be converted
  366. * to a String value, if necessary.
  367. *
  368. * @see {@link #get}
  369. * @memberOf mmir.ConfigurationManager.prototype
  370. */
  371. getString: function(propertyName, defaultValue, useSafeAccess){
  372. var val = this.get(propertyName, defaultValue, useSafeAccess);
  373. if(typeof val !== 'undefined'){
  374. if(typeof val === 'string'){
  375. return val;
  376. }
  377. else {
  378. return JSON.stringify(val);
  379. }
  380. }
  381. }
  382. };//END: return {...
  383. }//END: constructor = function(){...
  384. instance = new constructor();
  385. return instance;
  386. });