import * as assert from 'assert' ; import * as os from 'os' ; import { log } from 'napajs' ; import { Application , EntryPoint , Interceptor } from './application' ; import { ControlFlags , Request , RequestHelper } from './request' ; import { ResponseCode , Response , ResponseHelper , DebugInfo , DebugEvent } from './response' ; import { RequestTemplate } from './request-template' ; import { NamedObject } from './named-object' ; import { ScopedObjectContext , ScopedObjectContextDef } from './object-context' ; import { MetricCollection } from './metric' ; export class RequestContext { private _application : Application = null ; private _request : Request = null ; private _entryPoint : EntryPoint = null ; private _perRequestObjectContext : ScopedObjectContext = null ; private _logger : RequestLogger = null ; private _debugger : RequestDebugger = null ; private _perf : PerfDataCollector = null ; private _executionDepth : number = 0 ; private _executionStack : Interceptor [ ] ; public constructor ( base , request ) { assert ( base != null ) ; assert ( request != null ) ; request = RequestHelper . fromJsValue ( request ) ; this . _application = base . application ; this . _request = request ; let parentContext = base . objectContext ; let perRequestObjectContextDef = base . application . settings . allowPerRequestOverride ? new ScopedObjectContextDef ( parentContext . def , request . overrideTypes , request . overrideProviders , request . overrideObjects , false ) : new ScopedObjectContextDef ( parentContext . def , [ ] , [ ] , [ ] , false ) ; this . _perRequestObjectContext = new ScopedObjectContext ( "request" , parentContext . baseDir , parentContext , perRequestObjectContextDef ) ; this . _logger = new RequestLogger ( request . application + "." + request . entryPoint , request . traceId ) ; this . _debugger = new RequestDebugger ( ) ; this . _perf = new PerfDataCollector ( this . metric ) ; this . _entryPoint = this . getEntryPoint ( request . entryPoint ) ; if ( this . _entryPoint == null ) { throw new Error ( "Entrypoint does not exist: '" + request . entryPoint + "'" ) ; } this . _executionStack = this . prepareExecutionStack ( request . entryPoint ) ; this . _executionDepth = 0 ; } private prepareExecutionStack ( entryPointName ) { if ( this . _perRequestObjectContext . def . namedObjectDefs . length == 0 ) { if ( this . _entryPoint == null ) { throw new Error ( "Entrypoint '" + entryPointName + "' does not exist." ) ; } return this . _application . getExecutionStack ( entryPointName ) ; } let entryPointObject = this . getNamedObject ( entryPointName ) ; let interceptorNames = entryPointObject . def . value . executionStack ; if ( interceptorNames == null ) { interceptorNames = this . app . settings . defaultExecutionStack ; } if ( entryPointObject . scope !== 'request' ) { let oldStack = this . app . getExecutionStack ( entryPointName ) ; let newStack = [ ] ; assert ( oldStack . length == interceptorNames . length ) ; for ( let i = 0 ; i < oldStack . length ; ++ i ) { let interceptorName = interceptorNames [ i ] ; let interceptorObject = this . getNamedObject ( interceptorName ) ; if ( interceptorObject == null ) { throw ( "Interceptor '" + interceptorName + "' does not exist." ) ; } if ( interceptorObject . scope !== 'request' ) { newStack . push ( oldStack [ i ] ) ; } else { if ( interceptorObject . value == null || interceptorObject . def . value . _type !== 'Interceptor' ) { throw new Error ( "Bad override on interceptor '" + interceptorName + "', should be of Interceptor type and not null. " ) ; } newStack . push ( interceptorObject . value ) ; } } return newStack ; } let newStack = [ ] ; for ( let interceptorName of interceptorNames ) { let interceptor = this . getInterceptor ( interceptorName ) ; if ( interceptor == null ) { throw new Error ( "Interceptor '" + interceptorName + "' is not a valid interceptor." ) ; } newStack . push ( interceptor ) ; } return newStack ; } public async execute ( ) { return this . continueExecution ( ) ; } public async continueExecution ( ) { if ( this . _executionDepth < this . _executionStack . length ) { return this . _executionStack [ this . _executionDepth ++ ] ( this ) ; } return Promise . resolve ( { responseCode : ResponseCode . Success } ) ; } public get app ( ) : Application { return this . _application ; } public get request ( ) : Request { return this . _request ; } public get entryPoint ( ) : EntryPoint { return this . _entryPoint ; } public get entryPointName ( ) : string { return this . _request . entryPoint ; } public get traceId ( ) : string { return this . _request . traceId ; } public get controlFlags ( ) : ControlFlags { return this . _request . controlFlags ; } private get metric ( ) : MetricCollection { return this . _application . metrics ; } public get input ( ) : any { return this . _request . input ; } public get logger ( ) : RequestLogger { return this . _logger ; } public get debugger ( ) : RequestDebugger { return this . _debugger ; } public get perf ( ) : PerfDataCollector { return this . _perf ; } public create ( input ) { return this . _perRequestObjectContext . create ( input ) ; } public get ( name ) { let namedObject = this . getNamedObject ( name ) ; if ( namedObject != null ) { return namedObject . value ; } return undefined ; } public getNamedObject ( name ) { return this . _perRequestObjectContext . get ( name ) ; } public getEntryPoint ( entryPointName ) { let object = this . getNamedObject ( entryPointName ) ; if ( object != null && object . def . value . _type != 'EntryPoint' ) { throw new Error ( "Object '" + entryPointName + "' is not of EntryPoint type." ) ; } return object != null ? object . value : null ; } public getInterceptor ( name ) { let object = this . getNamedObject ( name ) ; if ( object != null && object . def . name !== 'Interceptor' ) { throw new Error ( "Object '" + name + "' is not of Interceptor type." ) ; } return object == null ? null : object . value ; } public getFunction ( functionName ) { let func = this . get ( functionName ) ; if ( func != null && typeof func !== 'function' ) { throw new Error ( "Object '" + functionName + "' is not a function." ) ; } return func ; } } export class RequestDebugger { public setLastError ( lastError ) { this . _lastError = lastError ; } public detail ( key , value ) { this . _details [ key ] = value ; } public event ( logLevel , message ) { this . _events . push ( { eventTime : new Date ( ) , logLevel : logLevel , message : message } ) ; } public getOutput ( ) { return { exception : this . _lastError == null ? undefined : { message : this . _lastError . message , stack : this . _lastError . stack , } , details : this . _details , events : this . _events , machineName : os . hostname ( ) , } ; } private _lastError : Error = null ; private _details : { [ key ] : any ; } = { } ; private _events : DebugEvent [ ] = [ ] ; } export class RequestLogger { public constructor ( sectionName , traceId ) { this . _sectionName = sectionName ; this . _traceId = traceId ; } public debug ( message ) { log . debug ( this . _sectionName , this . _traceId , message ) ; } public info ( message ) { log . info ( this . _sectionName , this . _traceId , message ) ; } public warn ( message ) { log . warn ( this . _sectionName , this . _traceId , message ) ; } public err ( message ) { log . err ( this . _sectionName , this . _traceId , message ) ; } private _traceId : string ; private _sectionName : string ; } export class PerfDataCollector { private _metrics : MetricCollection ; private _perfData : { [ name ] : number ; } = { } ; constructor ( metrics ) { assert ( metrics != null ) ; this . _metrics = metrics ; } public get data ( ) : { [ name ] : number ; } { return this . _perfData ; } public set ( metricName , value , dimensions ? ) { let metric = this . _metrics [ metricName ] ; if ( metric != null ) { metric . set ( value , dimensions ) ; } this . _perfData [ PerfDataCollector . getKeyName ( metricName , dimensions ) ] = value ; } public increment ( metricName , dimensions ? ) { let metric = this . _metrics [ metricName ] ; if ( metric != null ) { metric . increment ( dimensions ) ; } let key = PerfDataCollector . getKeyName ( metricName , dimensions ) ; if ( this . _perfData [ key ] == null ) { this . _perfData [ key ] = 1 ; } else { this . _perfData [ key ] ++ ; } } public decrement ( metricName , dimensions ? ) { let metric = this . _metrics [ metricName ] ; if ( metric != null ) { metric . decrement ( dimensions ) ; } let key = PerfDataCollector . getKeyName ( metricName , dimensions ) ; if ( this . _perfData [ key ] == null ) { this . _perfData [ key ] = - 1 ; } else { this . _perfData [ key ] -- ; } } public elapse ( metricName , func , dimensions ? ) { let start = process . hrtime ( ) ; let ret = func ( ) ; let duration = process . hrtime ( start ) ; let durationInUs = duration [ 0 ] * 1e6 + duration [ 1 ] / 1e3 ; let metric = this . _metrics [ metricName ] ; if ( metric != null ) { metric . set ( durationInUs , dimensions ) ; } this . _perfData [ PerfDataCollector . getKeyName ( metricName , dimensions ) ] = durationInUs ; return ret ; } private static getKeyName ( metricName , dimensions ? ) { if ( dimensions == null ) { return metricName ; } return metricName + "[" + dimensions . join ( ", " ) + "]" ; } }