'use strict' ; import { ServiceRepresentation , ServiceInstanceReference , DepInstances , Service , ProcessRepresentation } from '../domain' ; import assert = require ( 'assert' ) ; import { ServiceCore } from './ServiceCore' ; import { ProcessContext } from '../application/ProcessContext' ; const debug = require ( 'debug' ) ( 'pandora:ServiceReconciler' ) ; /**
 * Class ServiceReconciler
 */ export class ServiceReconciler { protected context : ProcessContext ; protected services : Map < string , ServiceInstanceReference > = new Map ; protected state : 'notBoot' | 'booting' | 'booted' | 'stoping' = 'notBoot' ; protected processRepresentation : ProcessRepresentation ; constructor ( processRepresentation , context ) { this . processRepresentation = processRepresentation ; this . context = context ; } param { ServiceRepresentation } serviceRepresentation public receiveServiceRepresentation ( serviceRepresentation ) { debug ( 'receiveServiceRepresentation() %j' , serviceRepresentation ) ; const id = serviceRepresentation . serviceName ; if ( this . services . has ( id ) ) { return ; } const ref = { serviceRepresentation : serviceRepresentation , state : 'noinstance' } ; this . services . set ( serviceRepresentation . serviceName , ref ) ; } param { "asc" | "desc" } order return { Array } public getOrderedServiceIdSet ( order ) { const ret = [ ] ; for ( const id of this . services . keys ( ) ) { ret . push ( { id , weight : this . getWeight ( id ) } ) ; } return ret . sort ( ( a , b ) => { if ( order === 'asc' ) { return a . weight - b . weight ; } else { return b . weight - a . weight ; } } ) ; } param id param { string [ ] } chain return { any } public getWeight ( id , chain ? ) { chain = Array . from ( chain || [ ] ) ; assert ( - 1 === chain . indexOf ( id ) , `Service name: ${ id } in a cyclic dependency chain: ${ chain . join ( ' -> ' ) } -> ${ id }` ) ; if ( chain . length > 1 && id === 'all' ) { throw new Error ( `Reserved service name 'all' not allowed to contains within a dependency chain: ${ chain . join ( ' -> ' ) } -> ${ id }` ) ; } if ( id === 'all' ) { return Infinity ; } chain . push ( id ) ; assert ( this . services . has ( id ) , `Could not found service id: ${ id }` ) ; const ref = this . services . get ( id ) ; const { serviceRepresentation } = ref ; if ( ! serviceRepresentation . dependencies || ! serviceRepresentation . dependencies . length ) { return 1 ; } else { const nextLevelWeights = [ ] ; for ( const nextId of serviceRepresentation . dependencies ) { nextLevelWeights . push ( this . getWeight ( nextId , chain ) ) ; } return Math . max . apply ( Math , nextLevelWeights ) + 1 ; } } /**
     * Instantiate all the services
     */ public instantiate ( ) { for ( const { id } of this . getOrderedServiceIdSet ( 'asc' ) ) { assert ( this . services . has ( id ) , `Could not found service id: ${ id }` ) ; const ref = this . services . get ( id ) ; const { state , serviceRepresentation } = ref ; debug ( 'instantiateOne() request %s' , id ) ; if ( state === 'noinstance' ) { debug ( 'instantiateOne() instantiate %s' , id ) ; const deps = serviceRepresentation . dependencies ; const depInstances = { } ; if ( deps ) { for ( let depId of deps ) { if ( depId === 'all' ) { continue ; } depInstances [ depId ] = this . services . get ( depId ) . serviceCoreInstance ; } } const serviceEntry = ( < any > serviceRepresentation . serviceEntry ) . getLazyClass ? ( < any > serviceRepresentation . serviceEntry ) . getLazyClass ( ) : serviceRepresentation . serviceEntry ; serviceRepresentation . config = serviceRepresentation . configResolver ? serviceRepresentation . configResolver ( this . context . processContextAccessor , serviceRepresentation . config ) : serviceRepresentation . config ; const serviceCoreInstance = new ServiceCore ( { context : this . context . processContextAccessor , representation : serviceRepresentation , depInstances : depInstances } , serviceEntry ) ; ref . serviceCoreInstance = serviceCoreInstance ; ref . state = 'instanced' ; debug ( 'instantiateOne() instanced %s' , id ) ; } } } return { Promise < void > } public async start ( ) { debug ( 'start()' ) ; this . instantiate ( ) ; if ( this . state !== 'booted' ) { this . state = 'booting' ; } for ( const { id } of this . getOrderedServiceIdSet ( 'asc' ) ) { assert ( this . services . has ( id ) , `Could not found service id: ${ id }` ) ; const ref = this . services . get ( id ) ; debug ( 'startOne() instanced request %s' , id ) ; assert ( ref . state !== 'noinstance' , 'instantiate first before start ' + id ) ; if ( ref . state === 'instanced' ) { debug ( 'startOne() start %s' , id ) ; ref . state = 'booting' ; const serviceCore = ref . serviceCoreInstance ; ref . serviceInstance = serviceCore . instantiate ( ) ; await serviceCore . start ( ) ; if ( ref . serviceRepresentation . publishToHub ) { await serviceCore . publish ( ) ; } ref . state = 'booted' ; debug ( 'startOne() booted %s' , id ) ; } } debug ( 'start() booted' ) ; this . state = 'booted' ; } public async stop ( ) { debug ( 'stop()' ) ; if ( this . state === 'notBoot' ) { return ; } for ( const { id } of this . getOrderedServiceIdSet ( 'desc' ) ) { assert ( this . services . has ( id ) , `Could not found service id: ${ id }` ) ; const ref = this . services . get ( id ) ; debug ( 'stopOne() instanced request %s' , id ) ; assert ( ref . state !== 'noinstance' , 'instantiate first before stop ' + id ) ; if ( ref . state === 'booted' ) { debug ( 'stopOne() start %s' , id ) ; ref . state = 'stopping' ; const serviceCore = ref . serviceCoreInstance ; await serviceCore . stop ( ) ; ref . serviceInstance = serviceCore . getService ( ) ; ref . state = 'instanced' ; debug ( 'startOne() stopped %s' , id ) ; } } debug ( 'stop() stopped' ) ; this . state = 'notBoot' ; } public get < T extends Service > ( id ) { assert ( this . services . has ( id ) , `Could not found service id: ${ id }` ) ; const ref = this . services . get ( id ) ; assert ( ref . serviceInstance , `Service id: ${ id } have not instance yet` ) ; return < T > ref . serviceInstance ; } public getServiceClass ( serviceName ) { const ref = this . services . get ( serviceName ) ; if ( ref ) { const serviceRepresentation = ref . serviceRepresentation ; const serviceEntry = ( < any > serviceRepresentation . serviceEntry ) . getLazyClass ? ( < any > serviceRepresentation . serviceEntry ) . getLazyClass ( ) : serviceRepresentation . serviceEntry ; return serviceEntry ; } return null ; } public getState ( ) { return this . state ; } }