config_initializer_class.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var App = require('app');
  19. /**
  20. * @typedef {object} initializer
  21. * @property {string} type initializer type name
  22. * @property {boolean} [isChecker] determines control flow callback
  23. */
  24. /**
  25. * @typedef {object} initializerType
  26. * @property {string} name key
  27. * @property {string} method function's name (prefer to start method-name with '_init' or '_initAs'). Each method here is called with arguments equal to <code>initialValue</code>-call args. Initializer-settings are added as last argument
  28. */
  29. /**
  30. * Minimal fields-list that should be in the config-object
  31. * There is no way to set value without any of them
  32. *
  33. * @typedef {object} configProperty
  34. * @property {string} name config's name
  35. * @property {number|string} value current value
  36. * @property {string} filename file name where this config is
  37. * @property {number|string} [recommendedValue] value which is recommended
  38. */
  39. /**
  40. * Basic class for config-initializers
  41. * Each child should fill <code>initializers</code> or <code>uniqueInitializers</code> and <code>initializerTypes</code>
  42. * Usage:
  43. * <pre>
  44. * var myCoolInitializer = App.ConfigInitializerClass.create({
  45. * initializers: {
  46. * 'my-cool-config': {
  47. * type: 'some_type'
  48. * }
  49. * },
  50. *
  51. * initializerTypes: {
  52. * some_type: {
  53. * method: '_initAsCool'
  54. * }
  55. * },
  56. *
  57. * _initAsCool: function (configProperty, localDB, dependencies, initializer) {
  58. * // some magic
  59. * return configProperty;
  60. * }
  61. * });
  62. *
  63. * var myConfig = { name: 'my-cool-config' };
  64. * var localDB = getLocalDB();
  65. * var dependencies = {};
  66. * myCoolInitializer.initialValue(myConfig, localDB, dependencies);
  67. * </pre>
  68. * <code>dependencies</code> - it's an object with almost any information that might be needed to set config's value. It
  69. * shouldn't contain App.*-flags like 'isHaEnabled' or 'isHadoop2Stack'. They might be accessed directly from App. But something
  70. * bigger data should be pulled to the <code>dependencies</code>.
  71. * Information about cluster's hosts, topology of master components should be in the <code>localDB</code>
  72. *
  73. * @type {ConfigInitializerClass}
  74. * @augments {Em.Object}
  75. */
  76. App.ConfigInitializerClass = Em.Object.extend({
  77. _initializerFlowCode: {
  78. next: 0,
  79. skipNext: 1,
  80. skipAll: 2
  81. },
  82. concatenatedProperties: ['initializerTypes'],
  83. /**
  84. * Map with configurations for config initializers
  85. * It's used only for initializers which are common for some configs (if not - use <code>uniqueInitializers</code>-map)
  86. * Key {string} configProperty-name
  87. * Value {initializer|initializer[]} settings for initializer
  88. *
  89. * @type {object}
  90. */
  91. initializers: {},
  92. /**
  93. * Map with initializers types
  94. * It's not overridden in the child-classes (@see Ember's concatenatedProperties)
  95. *
  96. * @type {initializerType[]}
  97. */
  98. initializerTypes: [],
  99. /**
  100. * Map with initializers that are used only for one config (are unique)
  101. * Key: configProperty-name
  102. * Value: method-name
  103. * Every method from this map is called with same arguments as <code>initialValue</code> is (prefer to start method-name with '_init' or '_initAs')
  104. *
  105. * @type {object}
  106. */
  107. uniqueInitializers: {},
  108. /**
  109. * Wrapper for common initializers
  110. * Execute initializer if it is a function or throw an error otherwise
  111. *
  112. * @param {configProperty} configProperty
  113. * @param {topologyLocalDB} localDB
  114. * @param {object} dependencies
  115. * @returns {Object}
  116. * @private
  117. */
  118. _defaultInitializer: function (configProperty, localDB, dependencies) {
  119. var args = [].slice.call(arguments);
  120. var self = this;
  121. var initializers = this.get('initializers');
  122. var initializerTypes = this.get('initializerTypes');
  123. var initializer = initializers[Em.get(configProperty, 'name')];
  124. if (initializer) {
  125. initializer = Em.makeArray(initializer);
  126. var i = 0;
  127. while(i < initializer.length) {
  128. var init = initializer[i];
  129. var _args = [].slice.call(args);
  130. var type = initializerTypes.findProperty('name', init.type);
  131. // add initializer-settings
  132. _args.push(init);
  133. var methodName = type.method;
  134. Em.assert('method-initializer is not a function ' + methodName, 'function' === Em.typeOf(self[methodName]));
  135. if (init.isChecker) {
  136. var result = self[methodName].apply(self, _args);
  137. if (result === this.flowSkipNext()) {
  138. i++; // skip next
  139. }
  140. else {
  141. if (result === this.flowSkipAll()) {
  142. break;
  143. }
  144. }
  145. }
  146. else {
  147. configProperty = self[methodName].apply(self, _args);
  148. }
  149. i++;
  150. }
  151. }
  152. return configProperty;
  153. },
  154. /**
  155. * Entry-point for any config's value initializing
  156. * Before calling it, be sure that <code>initializers</code> or <code>uniqueInitializers</code>
  157. * contains record about needed configs
  158. *
  159. * @param {configProperty} configProperty
  160. * @param {topologyLocalDB} localDB
  161. * @param {object} dependencies
  162. * @returns {Object}
  163. * @method initialValue
  164. */
  165. initialValue: function (configProperty, localDB, dependencies) {
  166. var configName = Em.get(configProperty, 'name');
  167. var initializers = this.get('initializers');
  168. var initializer = initializers[configName];
  169. if (initializer) {
  170. return this._defaultInitializer(configProperty, localDB, dependencies);
  171. }
  172. var uniqueInitializers = this.get('uniqueInitializers');
  173. var uniqueInitializer = uniqueInitializers[configName];
  174. if (uniqueInitializer) {
  175. var args = [].slice.call(arguments);
  176. return this[uniqueInitializer].apply(this, args);
  177. }
  178. Em.set(configProperty, 'initialValue', Em.get(configProperty, 'value'));
  179. return configProperty;
  180. },
  181. /**
  182. * Should do some preparing for initializing-process
  183. * Shouldn't be redefined without needs
  184. * Should be used before any <code>initialValue</code>-calls (if needed)
  185. *
  186. * @method setup
  187. */
  188. setup: Em.K,
  189. /**
  190. * Should restore Initializer's to the initial state
  191. * Basically, should revert changes done in the <code>setup</code>
  192. *
  193. * @method cleanup
  194. */
  195. cleanup: Em.K,
  196. /**
  197. * Setup <code>initializers</code>
  198. * There are some configs with names based on other config's values.
  199. * Example: config with name <code>hadoop.proxyuser.{{oozieUser}}.hosts</code>
  200. * Here <code>{{oozieUser}}</code> is value of the config <code>['oozie-env']['oozie_user']</code>.
  201. * So, when <code>initializers</code> are manually set up in the Initializer-instance, there is no way to populate initializer
  202. * for such configs. Reason: each initializer's key is a config-name which is compared strictly to the provided config-name.
  203. * Solution: use config-names with placeholders. Example: <code>hadoop.proxyuser.${oozieUser}.hosts</code>
  204. * In this case, before calling <code>initialValue</code> this key should be updated with real value. And it should be done
  205. * in the common way for the all such configs.
  206. * Code example:
  207. * <pre>
  208. * var mySettings = {
  209. * oozieUser: 'someDude'
  210. * };
  211. *
  212. * var myCoolInitializer = App.MoveComponentConfigInitializerClass.create({
  213. * initializers: {
  214. * 'hadoop.proxyuser.{{oozieUser}}.hosts': {
  215. * // some setting are here
  216. * }
  217. * },
  218. *
  219. * setup: function (settings) {
  220. * this._updateInitializers(settings);
  221. * },
  222. *
  223. * cleanup: function () {
  224. * this._restoreInitializers();
  225. * }
  226. *
  227. * });
  228. *
  229. * myCoolInitializer.setup(mySettings); // after this call `myCoolInitializer.initializers` will contain two keys
  230. * console.log(myCoolInitializer.initializers); // {'hadoop.proxyuser.{{oozieUser}}.hosts': {}, 'hadoop.proxyuser.someDude.hosts': {}}
  231. * // it's possible to call `initialValue` now
  232. * myCoolInitializer.initialValue({name: 'hadoop.proxyuser.someDude.hosts', value: ''}, {}, {});
  233. * myCoolInitializer.cleanup(); // after updating values for the all configs
  234. * </pre>
  235. * Additional key in the <code>initializers</code> won't cause any issues.
  236. * Keep in mind replacing-rules:
  237. * <ul>
  238. * <li>Each field in the <code>settings</code> should be a string or number</li>
  239. * <li>Substring that will be replaced should be wrapped with '{{', '}}'</li>
  240. * <li>Each field is searched in the each initializers-key, so try to avoid names-collision</li>
  241. * <li>Value for 'new' key is the same as for 'old' ('hadoop.proxyuser.{{oozieUser}}.hosts' and 'hadoop.proxyuser.someDude.hosts' will have same initializer)</li>
  242. * </ul>
  243. * <b>Important! Be sure, that you call <code>_restoreInitializers</code> before calling <code>_updateInitializers</code> second time</b>
  244. *
  245. * @param {object} settings
  246. * @method _updateInitializers
  247. */
  248. _updateInitializers: function (settings) {
  249. settings = settings || {};
  250. var originalInitializers = this.get('initializers');
  251. var copyInitializers = Em.copy(originalInitializers, true);
  252. this.set('__copyInitializers', copyInitializers);
  253. var initializers = this._updateNames('initializers', settings);
  254. this._setForComputed('initializers', initializers);
  255. var originalUniqueInitializers = this.get('uniqueInitializers');
  256. var copyUniqueInitializers = Em.copy(originalUniqueInitializers, true);
  257. this.set('__copyUniqueInitializers', copyUniqueInitializers);
  258. var uniqueInitializers = this._updateNames('uniqueInitializers', settings);
  259. this._setForComputed('uniqueInitializers', uniqueInitializers);
  260. },
  261. /**
  262. * Revert names changes done in the <code>_updateInitializers</code>
  263. *
  264. * @method _restoreInitializers
  265. */
  266. _restoreInitializers: function() {
  267. var copyInitializers = this.get('__copyInitializers');
  268. var copyUniqueInitializers = this.get('__copyUniqueInitializers');
  269. if ('object' === Em.typeOf(copyInitializers)) {
  270. this._setForComputed('initializers', Em.copy(copyInitializers, true));
  271. }
  272. if ('object' === Em.typeOf(copyUniqueInitializers)) {
  273. this._setForComputed('uniqueInitializers', Em.copy(copyUniqueInitializers, true));
  274. }
  275. },
  276. /**
  277. * Replace list of <code>settings</code> in the sourceKey
  278. * Example:
  279. * <pre>
  280. * var sourceKey = '{{A}},{{B}},{{C}}';
  281. * var settings = {A: 'a', B: 'b', C: 'c'};
  282. * sourceKey = _updateNames(sourceKey, settings);
  283. * console.log(sourceKey); // 'a,b,c'
  284. * </pre>
  285. *
  286. * @param {string} sourceKey
  287. * @param {object} settings
  288. * @returns {object}
  289. * @private
  290. * @method _updateNames
  291. */
  292. _updateNames: function (sourceKey, settings) {
  293. settings = settings || {};
  294. var source = this.get(sourceKey);
  295. Object.keys(source).forEach(function (configName) {
  296. var initializer = source[configName];
  297. Object.keys(settings).forEach(function (key) {
  298. var replaceWith = settings[key];
  299. var toReplace = '{{' + key + '}}';
  300. configName = configName.replace(toReplace, replaceWith);
  301. });
  302. source[configName] = initializer;
  303. });
  304. return source;
  305. },
  306. flowNext: function() {
  307. return this.get('_initializerFlowCode.next');
  308. },
  309. flowSkipNext: function() {
  310. return this.get('_initializerFlowCode.skipNext');
  311. },
  312. flowSkipAll: function() {
  313. return this.get('_initializerFlowCode.skipAll');
  314. },
  315. /**
  316. * Set value for computed property using `reopen`. Currently used to update 'initializers'
  317. * and 'uniqueInitializers'.
  318. * Used to set value for props like:
  319. * <code>cp: function() { }.property()</code>
  320. * <code>
  321. * var obj = App.ConfigInitializerClass.create({
  322. * cp: function() {
  323. * return {
  324. * key: "value"
  325. * }
  326. * }.property(),
  327. * setProp: function() {
  328. * this.set('cp', {newKey: "new_value"}); // will not change `cp` value
  329. * },
  330. * updateProp: function() {
  331. * this._setForComputed('cp', { newKey: "new_value"}); // will update
  332. * }
  333. * });
  334. *
  335. * obj.get('cp'); // {key: "value"}
  336. * obj.setProp();
  337. * obj.get('cp'); // {key: "value"}
  338. * obj.updateProp();
  339. * obj.get('cp'); // {newKey: "new_value"}
  340. * </code>
  341. * @private
  342. * @param {string} key
  343. * @param {*} value
  344. */
  345. _setForComputed: function(key, value) {
  346. var obj = {};
  347. obj[key] = function() {
  348. return value;
  349. }.property();
  350. this.reopen(obj);
  351. }
  352. });