blob: 0663bc3ee678a71e3d845d5a1b711a80b8b13779 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var App = require('app');
/**
* @typedef {object} initializer
* @property {string} type initializer type name
* @property {boolean} [isChecker] determines control flow callback
*/
/**
* @typedef {object} initializerType
* @property {string} name key
* @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
*/
/**
* Minimal fields-list that should be in the config-object
* There is no way to set value without any of them
*
* @typedef {object} configProperty
* @property {string} name config's name
* @property {number|string} value current value
* @property {string} filename file name where this config is
* @property {number|string} [recommendedValue] value which is recommended
*/
/**
* Basic class for config-initializers
* Each child should fill <code>initializers</code> or <code>uniqueInitializers</code> and <code>initializerTypes</code>
* Usage:
* <pre>
* var myCoolInitializer = App.ConfigInitializerClass.create({
* initializers: {
* 'my-cool-config': {
* type: 'some_type'
* }
* },
*
* initializerTypes: {
* some_type: {
* method: '_initAsCool'
* }
* },
*
* _initAsCool: function (configProperty, localDB, dependencies, initializer) {
* // some magic
* return configProperty;
* }
* });
*
* var myConfig = { name: 'my-cool-config' };
* var localDB = getLocalDB();
* var dependencies = {};
* myCoolInitializer.initialValue(myConfig, localDB, dependencies);
* </pre>
* <code>dependencies</code> - it's an object with almost any information that might be needed to set config's value. It
* shouldn't contain App.*-flags like 'isHaEnabled' or 'isHadoop2Stack'. They might be accessed directly from App. But something
* bigger data should be pulled to the <code>dependencies</code>.
* Information about cluster's hosts, topology of master components should be in the <code>localDB</code>
*
* @type {ConfigInitializerClass}
* @augments {Em.Object}
*/
App.ConfigInitializerClass = Em.Object.extend({
_initializerFlowCode: {
next: 0,
skipNext: 1,
skipAll: 2
},
concatenatedProperties: ['initializerTypes'],
/**
* Map with configurations for config initializers
* It's used only for initializers which are common for some configs (if not - use <code>uniqueInitializers</code>-map)
* Key {string} configProperty-name
* Value {initializer|initializer[]} settings for initializer
*
* @type {object}
*/
initializers: {},
/**
* Map with initializers types
* It's not overridden in the child-classes (@see Ember's concatenatedProperties)
*
* @type {initializerType[]}
*/
initializerTypes: [],
/**
* Map with initializers that are used only for one config (are unique)
* Key: configProperty-name
* Value: method-name
* Every method from this map is called with same arguments as <code>initialValue</code> is (prefer to start method-name with '_init' or '_initAs')
*
* @type {object}
*/
uniqueInitializers: {},
/**
* Wrapper for common initializers
* Execute initializer if it is a function or throw an error otherwise
*
* @param {configProperty} configProperty
* @param {topologyLocalDB} localDB
* @param {object} dependencies
* @returns {Object}
* @private
*/
_defaultInitializer: function (configProperty, localDB, dependencies) {
var args = [].slice.call(arguments);
var self = this;
var initializers = this.get('initializers');
var initializerTypes = this.get('initializerTypes');
var initializer = initializers[Em.get(configProperty, 'name')];
if (initializer) {
initializer = Em.makeArray(initializer);
var i = 0;
while(i < initializer.length) {
var init = initializer[i];
var _args = [].slice.call(args);
var type = initializerTypes.findProperty('name', init.type);
// add initializer-settings
_args.push(init);
var methodName = type.method;
Em.assert('method-initializer is not a function ' + methodName, 'function' === Em.typeOf(self[methodName]));
if (init.isChecker) {
var result = self[methodName].apply(self, _args);
if (result === this.flowSkipNext()) {
i++; // skip next
}
else {
if (result === this.flowSkipAll()) {
break;
}
}
}
else {
configProperty = self[methodName].apply(self, _args);
}
i++;
}
}
return configProperty;
},
/**
* Entry-point for any config's value initializing
* Before calling it, be sure that <code>initializers</code> or <code>uniqueInitializers</code>
* contains record about needed configs
*
* @param {configProperty} configProperty
* @param {topologyLocalDB} localDB
* @param {object} dependencies
* @returns {Object}
* @method initialValue
*/
initialValue: function (configProperty, localDB, dependencies) {
var configName = Em.get(configProperty, 'name');
var initializers = this.get('initializers');
var initializer = initializers[configName];
if (initializer) {
return this._defaultInitializer(configProperty, localDB, dependencies);
}
var uniqueInitializers = this.get('uniqueInitializers');
var uniqueInitializer = uniqueInitializers[configName];
if (uniqueInitializer) {
var args = [].slice.call(arguments);
return this[uniqueInitializer].apply(this, args);
}
Em.set(configProperty, 'initialValue', Em.get(configProperty, 'value'));
return configProperty;
},
/**
* Should do some preparing for initializing-process
* Shouldn't be redefined without needs
* Should be used before any <code>initialValue</code>-calls (if needed)
*
* @method setup
*/
setup: Em.K,
/**
* Should restore Initializer's to the initial state
* Basically, should revert changes done in the <code>setup</code>
*
* @method cleanup
*/
cleanup: Em.K,
/**
* Setup <code>initializers</code>
* There are some configs with names based on other config's values.
* Example: config with name <code>hadoop.proxyuser.{{oozieUser}}.hosts</code>
* Here <code>{{oozieUser}}</code> is value of the config <code>['oozie-env']['oozie_user']</code>.
* So, when <code>initializers</code> are manually set up in the Initializer-instance, there is no way to populate initializer
* for such configs. Reason: each initializer's key is a config-name which is compared strictly to the provided config-name.
* Solution: use config-names with placeholders. Example: <code>hadoop.proxyuser.${oozieUser}.hosts</code>
* In this case, before calling <code>initialValue</code> this key should be updated with real value. And it should be done
* in the common way for the all such configs.
* Code example:
* <pre>
* var mySettings = {
* oozieUser: 'someDude'
* };
*
* var myCoolInitializer = App.MoveComponentConfigInitializerClass.create({
* initializers: {
* 'hadoop.proxyuser.{{oozieUser}}.hosts': {
* // some setting are here
* }
* },
*
* setup: function (settings) {
* this._updateInitializers(settings);
* },
*
* cleanup: function () {
* this._restoreInitializers();
* }
*
* });
*
* myCoolInitializer.setup(mySettings); // after this call `myCoolInitializer.initializers` will contain two keys
* console.log(myCoolInitializer.initializers); // {'hadoop.proxyuser.{{oozieUser}}.hosts': {}, 'hadoop.proxyuser.someDude.hosts': {}}
* // it's possible to call `initialValue` now
* myCoolInitializer.initialValue({name: 'hadoop.proxyuser.someDude.hosts', value: ''}, {}, {});
* myCoolInitializer.cleanup(); // after updating values for the all configs
* </pre>
* Additional key in the <code>initializers</code> won't cause any issues.
* Keep in mind replacing-rules:
* <ul>
* <li>Each field in the <code>settings</code> should be a string or number</li>
* <li>Substring that will be replaced should be wrapped with '{{', '}}'</li>
* <li>Each field is searched in the each initializers-key, so try to avoid names-collision</li>
* <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>
* </ul>
* <b>Important! Be sure, that you call <code>_restoreInitializers</code> before calling <code>_updateInitializers</code> second time</b>
*
* @param {object} settings
* @method _updateInitializers
*/
_updateInitializers: function (settings) {
settings = settings || {};
var originalInitializers = this.get('initializers');
var copyInitializers = Em.copy(originalInitializers, true);
this.set('__copyInitializers', copyInitializers);
var initializers = this._updateNames('initializers', settings);
this._setForComputed('initializers', initializers);
var originalUniqueInitializers = this.get('uniqueInitializers');
var copyUniqueInitializers = Em.copy(originalUniqueInitializers, true);
this.set('__copyUniqueInitializers', copyUniqueInitializers);
var uniqueInitializers = this._updateNames('uniqueInitializers', settings);
this._setForComputed('uniqueInitializers', uniqueInitializers);
},
/**
* Revert names changes done in the <code>_updateInitializers</code>
*
* @method _restoreInitializers
*/
_restoreInitializers: function() {
var copyInitializers = this.get('__copyInitializers');
var copyUniqueInitializers = this.get('__copyUniqueInitializers');
if ('object' === Em.typeOf(copyInitializers)) {
this._setForComputed('initializers', Em.copy(copyInitializers, true));
}
if ('object' === Em.typeOf(copyUniqueInitializers)) {
this._setForComputed('uniqueInitializers', Em.copy(copyUniqueInitializers, true));
}
},
/**
* Replace list of <code>settings</code> in the sourceKey
* Example:
* <pre>
* var sourceKey = '{{A}},{{B}},{{C}}';
* var settings = {A: 'a', B: 'b', C: 'c'};
* sourceKey = _updateNames(sourceKey, settings);
* console.log(sourceKey); // 'a,b,c'
* </pre>
*
* @param {string} sourceKey
* @param {object} settings
* @returns {object}
* @private
* @method _updateNames
*/
_updateNames: function (sourceKey, settings) {
settings = settings || {};
var source = this.get(sourceKey);
Object.keys(source).forEach(function (configName) {
var initializer = source[configName];
Object.keys(settings).forEach(function (key) {
var replaceWith = settings[key];
var toReplace = '{{' + key + '}}';
configName = configName.replace(toReplace, replaceWith);
});
source[configName] = initializer;
});
return source;
},
flowNext: function() {
return this.get('_initializerFlowCode.next');
},
flowSkipNext: function() {
return this.get('_initializerFlowCode.skipNext');
},
flowSkipAll: function() {
return this.get('_initializerFlowCode.skipAll');
},
/**
* Set value for computed property using `reopen`. Currently used to update 'initializers'
* and 'uniqueInitializers'.
* Used to set value for props like:
* <code>cp: function() { }.property()</code>
* <code>
* var obj = App.ConfigInitializerClass.create({
* cp: function() {
* return {
* key: "value"
* }
* }.property(),
* setProp: function() {
* this.set('cp', {newKey: "new_value"}); // will not change `cp` value
* },
* updateProp: function() {
* this._setForComputed('cp', { newKey: "new_value"}); // will update
* }
* });
*
* obj.get('cp'); // {key: "value"}
* obj.setProp();
* obj.get('cp'); // {key: "value"}
* obj.updateProp();
* obj.get('cp'); // {newKey: "new_value"}
* </code>
* @private
* @param {string} key
* @param {*} value
*/
_setForComputed: function(key, value) {
var obj = {};
obj[key] = function() {
return value;
}.property();
this.reopen(obj);
}
});