import * as path from 'path' ; import * as assert from 'assert' ; import * as utils from './utils' ; import { NamedObjectDef , NamedObject , NamedObjectConfig , ScopedObjectContextDef , ScopedObjectContext , TypeDef , TypeConfig , ProviderDef , ProviderConfig , } from './object-model' ; import { MetricDef , MetricCollection , MetricConfig , createMetricCollection } from './metric' ; import { RequestContext } from './request-context' ; import { Response } from './response' ; import { RequestTemplate } from './request-template' ; export interface Settings { allowPerRequestOverride : boolean ; objectContextDef : ScopedObjectContextDef ; defaultExecutionStack ? : string [ ] ; baseDir : string ; } export type EntryPoint = ( requestContext ? : RequestContext , input ? : any ) => any ; export type Interceptor = ( context : RequestContext ) => Promise < Response > ; export interface ApplicationSettings extends Settings { id : string ; description ? : string ; metrics : MetricDef [ ] ; } export class Application { private _settings : ApplicationSettings ; private _perAppObjectContext : ScopedObjectContext ; private _defaultExecutionStack : Interceptor [ ] ; private _defaultRequestTemplate : RequestTemplate ; private _metrics : MetricCollection ; private _perEntryPointExecutionStack : Map < string , Interceptor [ ] > ; public constructor ( hostContext , settings ) { this . _settings = settings ; this . _perAppObjectContext = new ScopedObjectContext ( "./application" , settings . baseDir , hostContext , settings . objectContextDef ) ; this . _defaultExecutionStack = [ ] ; for ( let interceptorName of this . _settings . defaultExecutionStack ) { let interceptor = this . getInterceptor ( interceptorName ) ; if ( interceptor == null ) { throw new Error ( "Interceptor does not exisit: '" + interceptorName + "'." ) ; } this . _defaultExecutionStack . push ( interceptor ) ; } this . _perEntryPointExecutionStack = new Map < string , Interceptor [ ] > ( ) ; this . _perAppObjectContext . forEach ( object => { if ( object . def . value . _type === 'EntryPoint' ) { let executionStack = this . _defaultExecutionStack ; let customStack = object . def . value . executionStack ; if ( customStack != null ) { executionStack = [ ] ; for ( let interceptorName of < string [ ] > ( customStack ) ) { let interceptor = this . getInterceptor ( interceptorName ) ; if ( interceptor == null ) { throw new Error ( "Interceptor does not exist: '" + interceptorName + "'" ) ; } executionStack . push ( interceptor ) ; } } this . _perEntryPointExecutionStack . set ( object . def . name , executionStack ) ; } } ) ; this . _defaultRequestTemplate = new RequestTemplate ( this . id , this , undefined , { application : this . id , overrideTypes : [ ] , overrideProviders : [ ] , overrideObjects : [ ] } ) ; this . _metrics = { } ; if ( settings . metrics != null ) { this . _metrics = createMetricCollection ( settings . metrics ) ; } } public get id ( ) : string { return this . _settings . id ; } public get description ( ) : string { return this . _settings . description ; } public get settings ( ) : ApplicationSettings { return this . _settings ; } public get objectContext ( ) : ScopedObjectContext { return this . _perAppObjectContext ; } public get defaultExecutionStack ( ) : Interceptor [ ] { return this . _defaultExecutionStack ; } public get defaultRequestTemplate ( ) : RequestTemplate { return this . _defaultRequestTemplate ; } public getExecutionStack ( entryPointName ) { if ( this . _perEntryPointExecutionStack . has ( entryPointName ) ) { return this . _perEntryPointExecutionStack . get ( entryPointName ) ; } return null ; } public get metrics ( ) : MetricCollection { return this . _metrics ; } public create ( input ) { return this . _perAppObjectContext . create ( input ) ; } public get ( name ) { let namedObject = this . getNamedObject ( name ) ; if ( namedObject != null ) { return namedObject . value ; } return null ; } public getNamedObject ( name ) { return this . _perAppObjectContext . 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. Encountered: '" + object . def . name + "'." ) ; } return object != null ? object . value : null ; } public getInterceptor ( name ) { let object = this . getNamedObject ( name ) ; if ( object != null && object . def . value . _type !== 'Interceptor' ) { throw new Error ( "Object '" + name + "' is not of Interceptor type. Encountered: '" + object . def . name + "'." ) ; } return object == null ? null : object . value ; } public getFunction ( functionName ) { let object = this . getNamedObject ( functionName ) ; let fun = object == null ? null : object . value ; if ( fun != null && typeof fun !== 'function' ) { throw new Error ( "Object '" + functionName + "' is not a function." ) ; } return fun ; } } const SCHEMA_DIR = path . resolve ( __dirname , '../schema' ) ; export class ApplicationConfig { private static readonly APP_CONFIG_SCHEMA : utils . JsonSchema = new utils . JsonSchema ( path . resolve ( SCHEMA_DIR , "application-config.schema.json" ) ) ; public static fromConfigObject ( hostSettings , jsValue , basePath ) { utils . ensureSchema ( jsValue , this . APP_CONFIG_SCHEMA ) ; let appSettings = { baseDir : basePath , id : jsValue . id , description : jsValue . description , allowPerRequestOverride : jsValue . allowPerRequestOverride , defaultExecutionStack : jsValue . defaultExecutionStack , objectContextDef : hostSettings . objectContextDef , metrics : [ ] } ; if ( appSettings . allowPerRequestOverride == null ) { appSettings . allowPerRequestOverride = hostSettings . allowPerRequestOverride ; } if ( appSettings . defaultExecutionStack == null ) { appSettings . defaultExecutionStack = hostSettings . defaultExecutionStack ; } let typeDefFiles = jsValue . objectTypes ; let typeDefinitions = [ ] ; let typeToFileName = { } ; for ( let typeDefFile of typeDefFiles ) { let typeDefs = TypeConfig . fromConfig ( path . resolve ( basePath , typeDefFile ) ) ; for ( let typeDefinition of typeDefs ) { if ( typeToFileName . hasOwnProperty ( typeDefinition . typeName ) && ! typeDefinition . override ) { throw new Error ( "Object type '" + typeDefinition . typeName + "' already exists in file '" + typeToFileName [ typeDefinition . typeName ] + "'. Did you forget to set property 'override' to true? " ) ; } typeDefinitions . push ( typeDefinition ) ; typeToFileName [ typeDefinition . typeName ] = typeDefFile ; } } let providerDefFiles = jsValue . objectProviders ; let providerDefinitions = [ ] ; let protocolToFileName = { } ; if ( providerDefFiles != null ) { for ( let providerDefFile of providerDefFiles ) { let providerDefs = ProviderConfig . fromConfig ( path . resolve ( basePath , providerDefFile ) ) ; for ( let providerDef of providerDefs ) { if ( protocolToFileName . hasOwnProperty ( providerDef . protocol ) && ! providerDef . override ) { throw new Error ( "Object provider with protocol '" + providerDef . protocol + "' already exists in file '" + protocolToFileName [ providerDef . protocol ] + "' .Did you forget to set property 'override' to true? " ) ; } providerDefinitions . push ( providerDef ) ; protocolToFileName [ providerDef . protocol ] = providerDefFile ; } } } let namedObjectDefFiles = jsValue . namedObjects ; let namedObjectDefinitions = [ ] ; let nameToFileName = { } ; for ( let namedObjectDefFile of namedObjectDefFiles ) { let objectDefs = NamedObjectConfig . fromConfig ( path . resolve ( basePath , namedObjectDefFile ) ) ; for ( let objectDef of objectDefs ) { if ( nameToFileName . hasOwnProperty ( objectDef . name ) && ! objectDef . override ) { throw new Error ( "Named object'" + objectDef . name + "' already exists in file '" + nameToFileName [ objectDef . name ] + "'. Did you forget to set property 'override' to true? " ) ; } namedObjectDefinitions . push ( objectDef ) ; nameToFileName [ objectDef . name ] = namedObjectDefFile ; } } appSettings . objectContextDef = new ScopedObjectContextDef ( hostSettings . objectContextDef , typeDefinitions , providerDefinitions , namedObjectDefinitions , true ) ; let metricDefObject = jsValue . metrics ; if ( metricDefObject != null ) { let sectionName = metricDefObject . sectionName ; let metricDefFiles = metricDefObject . definition ; let metricToFilename = { } ; metricDefFiles . forEach ( metricDefFile => { let metricDefs = MetricConfig . fromConfig ( sectionName , path . resolve ( basePath , metricDefFile ) ) ; metricDefs . forEach ( metricDef => { if ( metricToFilename . hasOwnProperty ( metricDef . name ) ) { throw new Error ( "Metric '" + metricDef . name + "' already defined in file '" + metricToFilename [ metricDef . name ] + "'." ) ; } appSettings . metrics . push ( metricDef ) ; metricToFilename [ metricDef . name ] = metricDefFile ; } ) ; } ) ; } return appSettings ; } public static fromConfig ( parentSettings , appConfigFile ) { return utils . appendMessageOnException ( "Error found in application definition file '" + appConfigFile + "'." , ( ) => { return this . fromConfigObject ( parentSettings , utils . readConfig ( appConfigFile ) , path . dirname ( appConfigFile ) ) ; } ) ; } }