js/topology/define/topology_define_item.js
// NPM IMPORTS
import assert from 'assert'
// COMMON IMPORTS
import T from '../../utils/types'
import {is_browser} from '../../utils/is_browser'
let forge = undefined
if ( is_browser() )
{
forge = require('forge-browser').forge
} else {
forge = require('node-forge')
}
// COMMON IMPORTS
import CollectionVersion from '../../base/collection_version'
import Instance from '../../base/instance'
const context = 'common/topology/define/topology_define_item'
/**
* TopologyDefineItem class.
*
* @author Luc BORIES
* @license Apache-2.0
*
* @example
* A Topology item is a runtime dynamic object corresponding to a static topology registry item.
* Each item has a name, a tenant, a package, a version and initial settings.
* Item sub class instances can have collections of children items.
*
* Topology registry is shared and synchronized between all topology nodes with a master node to acknoledge changes.
* Topology runtime is the dynamic corresponding part of a topology registry and is instancied on each node.
*
* API:
* ->load():nothing - load Topology item settings.
* ->get_topology_info(arg_deep=true, arg_visited={}):object - get topology item informations.
* ->get_children():object - gt topology children items.
*
* ->get_topology_type():string - get resource topology type.
* ->get_topology_tenant():string - get resource topology tenant.
* ->get_topology_package():string - get resource topology package.
* ->get_topology_version():string - get resource topology version.
* ->get_topology_uid_desc():string - get resource topology uid description.
* ->get_topology_uid():string - get resource topology uid.
* ->get_topology_security():object - get resource topology security.
*
*
*/
export default class TopologyDefineItem extends Instance
{
/**
* Create a topology item instance.
*
* @param {string} arg_name - instance name.
* @param {object} arg_settings - instance settings map.
* @param {string} arg_class - class name.
* @param {string} arg_log_context - trace context string.
*
* @returns {nothing}
*/
constructor(arg_name, arg_settings, arg_class, arg_log_context)
{
const log_context = arg_log_context ? arg_log_context : context
assert( T.isObject(arg_settings), log_context + ':bad settings object')
// DECORATE STATE WITH A VERSION
if ( (! arg_settings.get) && arg_settings && arg_settings.state && !arg_settings.state.state_version)
{
arg_settings.state.state_version = 0
} else if ( arg_settings.setIn && arg_settings.has('state') && ! arg_settings.hasIn(['state', 'state_version']) )
{
arg_settings = arg_settings.setIn(['state', 'state_version'], 0)
}
super('defined_topology', arg_class ? arg_class : 'TopologyDefineItem', arg_name, arg_settings, log_context)
this.is_topology_define_item = true
// DEFINE DEFAULT ATTRIBUTES VALUES
this.DEFAULT_TYPE = 'item'
this.DEFAULT_TENANT = 'default'
this.DEFAULT_PACKAGE = 'default'
this.DEFAULT_VERSION = '0.0.0'
this.DEFAULT_SECURITY = {}
// SET ATTRIBUTES VALUES
this.topology_type = this.get_setting('type', this.DEFAULT_TYPE)
this.topology_tenant = this.get_setting('tenant', this.DEFAULT_TENANT)
this.topology_package = this.get_setting('package', this.DEFAULT_PACKAGE)
this.topology_version = this.get_setting('version', this.DEFAULT_VERSION)
this.topology_uid_desc = 'uid:' + this.topology_tenant + '/' + this.topology_package + '/' + this.get_name() + '/' + this.topology_version
const md = forge.md.sha1.create()
md.update(this.topology_uid_desc)
this.topology_uid = md.digest().toHex()
this.topology_security = this.get_setting('security', this.DEFAULT_SECURITY)
this.topology_children = {}
this.topology_owner = undefined
// console.log((arg_class ? arg_class : 'TopologyDefineItem') + ':' + arg_name, this.is_trace_enabled ? 'trace enabled' : 'trace disabled')
}
/**
* Load Topology item settings.
*
* @returns {Promise} - Promise(boolean): true=success, false=failure
*/
load()
{
// this.enable_trace()
this.enter_group('load')
super.load()
const promises = []
const children_names = Object.keys(this.topology_children)
this.debug('load:children_names=' + children_names)
children_names.forEach(
(child_name)=>{
this.debug('load:child=' + child_name)
const child_collection = this.topology_children[child_name]
const child_promise = this.load_collection(child_collection.plural, child_collection, child_collection.item_class, child_collection.item_init)
promises.push(child_promise)
}
)
if (promises.length == 0)
{
this.leave_group('load:async is resolved without item children with success')
return Promise.resolve(true)
}
this.leave_group('load:async wait')
return Promise.all(promises).then(
(result)=>{
// this.disable_trace()
this.leave_group('load:async is resolved with ' + (result ? 'success' : 'failure'))
return result
}
)
}
/**
* Check functional format.
*
* @returns {boolean}
*/
is_valid()
{
return true
}
/**
* Compare two versions.
* @TODO: use node-semver
*
* @param {string} arg_op - comparison operator (<, <=, =, >=, >).
* @param {string} arg_version1 - left version.
* @param {string} arg_version2 - right version.
*
* @returns {boolean}
*/
compare_versions(arg_op, arg_version1, arg_version2)
{
switch(arg_op)
{
case '<': return arg_version1 < arg_version2
case '<=': return arg_version1 <= arg_version2
case '>': return arg_version1 > arg_version2
case '>=': return arg_version1 >= arg_version2
case '=': return arg_version1 == arg_version2
}
return false
}
/**
* Load a collection of topology items.
*
* @param {string} arg_plural_name - plural name of the collection.
* @param {string} arg_single_name - single name of the collection item.
* @param {class} arg_topology_class - item TopologyDefineItem child class.
* @param {function} arg_init_cb - optional callback to complete item loading:(name,settings)=>Promise(boolean).
*
* @returns {nothing}
*/
declare_collection(arg_plural_name, arg_single_name, arg_topology_class, arg_init_cb)
{
this.enter_group('declare_collection of ' + arg_plural_name)
// CHECK ARGS
assert( T.isString(arg_plural_name) && arg_plural_name.length > 0, context + ':declare_collection:bad plural name for plural=' + arg_plural_name + ',single=' + arg_single_name)
assert( T.isString(arg_single_name) && arg_single_name.length > 0, context + ':declare_collection:bad single string for plural=' + arg_plural_name + ',single=' + arg_single_name)
assert( T.isFunction(arg_topology_class), context + ':declare_collection:bad class object for plural=' + arg_plural_name + ',single=' + arg_single_name)
// DECLARE COLLECTION
// const filter_version = (arg_name, arg_version)=>{
// return (arg_item)=>{
// return arg_item.get_name() == arg_name && arg_item.get_topology_version() == arg_version
// }
// }
this.topology_children[arg_plural_name] = {
is_versionned_collection: true,
plural:arg_plural_name,
single:arg_single_name,
item_class:arg_topology_class,
item_init:arg_init_cb,
collection:new CollectionVersion(),
has:(arg_name, arg_version)=>{
if (arg_version == 'latest')
{
return this.topology_children[arg_plural_name].collection.has(arg_name)
}
return this.topology_children[arg_plural_name].collection.has_version(arg_name, arg_version)
},
get:(arg_name, arg_version)=>{
if (arg_version == 'latest')
{
return this.topology_children[arg_plural_name].collection.get_latest_item(arg_name)
}
return this.topology_children[arg_plural_name].collection.get_item_of_version(arg_name, arg_version)
},
add:(arg_item)=>{
this.debug('collection[' + arg_plural_name + '].add item[' + arg_item.get_name() + ']')
this.topology_children[arg_plural_name].collection.add(arg_item)
},
remove:(arg_item)=>{
this.debug('collection[' + arg_plural_name + '].remove item[' + arg_item.get_name() + ']')
this.topology_children[arg_plural_name].collection.remove(arg_item)
}
}
// DECLARE ACCESSORS
this[arg_plural_name] = ()=>{
return this.topology_children[arg_plural_name].collection
}
this[arg_single_name] = (arg_name, arg_version='latest')=>{
// console.log('find item [' + arg_name + '] of version [' + arg_version + '] of collection [' + arg_plural_name + ']')
if (arg_version == 'latest')
{
return this.topology_children[arg_plural_name].collection.get_latest_item(arg_name)
}
return this.topology_children[arg_plural_name].collection.get_item_of_version(arg_name, arg_version)
}
this.leave_group('declare_collection of ' + arg_plural_name)
}
/**
* Load a collection of topology items.
*
* @param {string} arg_collection_name - name of the collection in the item settings.
* @param {object} arg_collection - child collection of items.
* @param {class} arg_topology_class - item TopologyDefineItem child class.
* @param {function} arg_init_cb - optional callback to complete item loading:(name,settings)=>Promise(boolean).
*
* @returns {Promise} - Promise(boolean): true=success, false=failure.
*/
load_collection(arg_collection_name, arg_collection, arg_topology_class, arg_init_cb)
{
this.enter_group('load_collection of ' + arg_collection_name)
// console.info('load_collection of ' + arg_collection_name)
const runtime = this.get_runtime()
const logger_manager = this.get_logger_manager()
assert( T.isObject(runtime) && runtime.is_server_runtime, context + ':load_collection:bad runtime instance')
assert( T.isObject(logger_manager) && logger_manager.is_logger_manager, context + ':load_collection:bad logger_manager instance')
const promises = []
// TODO ???
const collection_settings = this.get_setting(arg_collection_name, this.get_setting(['resources_by_type', arg_collection_name], undefined))
if (collection_settings)
{
const all_map = collection_settings.toMap()
all_map.forEach(
(settings, name) => {
this.info('Processing ' + arg_collection_name + ' item creation of:' + name)
// console.log(settings, 'settings')
// console.log(name, 'name')
if (! settings.has('tenant'))
{
settings = settings.set('tenant', this.get_topology_tenant())
}
if (! settings.has('package'))
{
settings = settings.set('package', this.get_topology_package())
}
settings = settings.set('name', name)
settings = settings.set('runtime', runtime)
settings = settings.set('logger_manager', logger_manager)
this.info('Creating ' + arg_collection_name + ' item ' + name)
const item = new arg_topology_class(name, settings)
item.topology_owner = this
arg_collection.add(item)
this.info('Loading ' + arg_collection_name + ' item ' + name)
let load_promise = item.load()
if ( T.isFunction(arg_init_cb) )
{
load_promise = load_promise.then(
(result)=>{
if (result)
{
return arg_init_cb(item)
}
return false
}
)
}
load_promise = load_promise.then(
(result)=>{
return result && item.is_valid()
}
)
promises.push(load_promise)
}
)
}
this.leave_group('load_collection of ' + arg_collection_name + ':async wait')
return Promise.all(promises).then(
(result)=>{
this.leave_group('load_collection of ' + arg_collection_name + ':async is resolved with ' + (result ? 'success' : 'failure'))
return result
}
)
}
/**
* Get exporting settings.
*
* @returns {object} - exported settings for browser without sensitive values.
*/
export_settings()
{
const settings = this.get_settings_js()
return settings
}
/**
* Get topology item informations.
*
* @param {boolean} arg_deep - get deep sub items information on true (default:true).
* @param {object} arg_visited - visited items plain object map (default:{}).
*
* @returns {object} - topology informations (plain object).
*/
get_topology_info(arg_deep=true, arg_visited={})
{
const info = {
name:this.get_name(),
uid_desc:this.topology_uid_desc,
uid:this.topology_uid,
tenant:this.topology_tenant,
package:this.topology_package,
version:this.topology_version,
type:this.topology_type,
security:this.topology_security,
children:this.get_children_names()
}
if ( arg_visited && (this.topology_uid in arg_visited) )
{
return Object.assign(info, { note:'already dumped' } )
}
arg_visited[this.topology_uid] = info
if (arg_deep)
{
const children_names = info.children
info.children = []
children_names.forEach(
(child_name) => {
let child = this.topology_children[child_name]
// CHILD IS NOT FOUND
if (! child)
{
console.error(context + ':get_topology_info:child not found [' + child_name + '] for item [' + info.name + ']')
return
}
// CHILD IS A TOPOLOGY DEFINE ITEM
if ( child && T.isFunction(child.get_topology_info) )
{
info.children.push( child.get_topology_info(arg_deep, arg_visited) )
return
}
// CHILD IS A VERSIONNED COLLECTION
if ( T.isObject(child) && child.is_versionned_collection)
{
const all_versioned_items = child.collection.get_all()
all_versioned_items.forEach(
(item)=>{
info.children.push( item.get_topology_info() )
}
)
}
}
)
}
return info
}
/**
* Dump topology item informations.
*
* @param {object} arg_info - topology info object.
* @param {string} arg_tabs - separator.
* @param {string} arg_eol - end of line char.
* @param {function} arg_write - output function.
*
* @returns {nothing}
*/
dump_topology_info(arg_info, arg_tabs=' ', arg_eol='\n', arg_write = console.info)
{
arg_write(arg_tabs + '-------------------------------------' + arg_eol)
arg_write(arg_tabs + 'name:' + arg_info.name + arg_eol)
arg_write(arg_tabs + 'uid_desc:' + arg_info.uid_desc + arg_eol)
arg_write(arg_tabs + 'uid:' + arg_info.uid + arg_eol)
arg_write(arg_tabs + 'tenant:' + arg_info.tenant + arg_eol)
arg_write(arg_tabs + 'package:' + arg_info.package + arg_eol)
arg_write(arg_tabs + 'version:' + arg_info.version + arg_eol)
arg_write(arg_tabs + 'type:' + arg_info.type + arg_eol)
arg_write(arg_tabs + 'security:' + arg_info.security + arg_eol)
arg_write(arg_tabs + 'children: [' + arg_eol)
arg_info.children.forEach(
(child_info)=>{
this.dump_topology_info(child_info, arg_tabs + ' ', arg_eol, arg_write)
}
)
arg_write(arg_tabs + ']' + arg_eol)
}
/**
* Get children names.
*
* @returns {array}
*/
get_children_names()
{
return Object.keys(this.topology_children)
}
/**
* Get resource topology type.
*
* @returns {string}
*/
get_topology_type()
{
return this.topology_type
}
/**
* Get resource topology tenant.
*
* @returns {string}
*/
get_topology_tenant()
{
return this.topology_tenant
}
/**
* Get resource topology package.
*
* @returns {string}
*/
get_topology_package()
{
return this.topology_package
}
/**
* Get resource topology version.
*
* @returns {string}
*/
get_topology_version()
{
return this.topology_version
}
/**
* Get resource topology uid description.
*
* @returns {string}
*/
get_topology_uid_desc()
{
return this.topology_uid_desc
}
/**
* Get resource topology uid.
*
* @returns {string}
*/
get_topology_uid()
{
return this.topology_uid
}
/**
* Get resource topology security.
*
* @returns {object}
*/
get_topology_security()
{
return this.topology_security
}
/**
* Get topology children items.
*
* @returns {object} - map of children items.
*/
get_children()
{
return this.topology_children
}
/**
* Get topology owner.
*
* @returns {string}
*/
get_topology_owner()
{
return this.topology_owner
}
}