import { DataProvider , LinkElementsParams , FilterParams } from '../provider' ; import { Dictionary , ClassModel , LinkType , ElementModel , LinkModel , LinkCount , Property , PropertyModel , LocalizedString , ElementIri , ElementTypeIri , LinkTypeIri , PropertyTypeIri , } from '../model' ; import { CompositeResponse , mergeClassTree , mergePropertyInfo , mergeClassInfo , mergeLinkTypesInfo , mergeLinkTypes , mergeElementInfo , mergeLinksInfo , mergeLinkTypesOf , mergeLinkElements , mergeFilter , } from './mergeUtils' ; export interface DPDefinition { name : string ; dataProvider : DataProvider ; useInStats ? : boolean ; } function isDefinition ( dp ) { const definition = dp as Partial < DPDefinition > ; return definition . name !== undefined && definition . dataProvider !== undefined ; } export type MergeMode = 'fetchAll' | 'sequentialFetching' ; export class CompositeDataProvider implements DataProvider { public dataProviders : DPDefinition [ ] ; public mergeMode : MergeMode = 'fetchAll' ; constructor ( dataProviders , params ? ) { let dpCounter = 1 ; this . dataProviders = dataProviders . map ( dp => { if ( isDefinition ( dp ) ) { return dp ; } else { return { name : 'dataProvider_' + dpCounter ++ , dataProvider : dp , } ; } } ) ; if ( params && params . mergeMode ) { this . mergeMode = params . mergeMode ; } } classTree ( ) { return this . fetchSequentially ( 'classTree' , mergeClassTree , undefined ) ; } propertyInfo ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'propertyInfo' , mergePropertyInfo , params ) ; } else { let propertyIds = params . propertyIds ; return this . queueProcessResults ( ( previousResult , dp ) => { propertyIds = propertyIds . filter ( id => ! previousResult || ! previousResult [ id ] ) ; return propertyIds . length > 0 ? dp . dataProvider . propertyInfo ( { propertyIds : propertyIds } ) : undefined ; } ) . then ( mergePropertyInfo ) ; } } classInfo ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'classInfo' , mergeClassInfo , params ) ; } else { let classIds = params . classIds ; return this . queueProcessResults ( ( previousResult , dp ) => { classIds = classIds . filter ( id => ! previousResult || previousResult . map ( cm => cm . id ) . indexOf ( id ) === - 1 ) ; return classIds . length > 0 ? dp . dataProvider . classInfo ( { classIds : classIds } ) : undefined ; } ) . then ( mergeClassInfo ) ; } } linkTypesInfo ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'linkTypesInfo' , mergeLinkTypesInfo , params ) ; } else { let linkTypeIds = params . linkTypeIds ; return this . queueProcessResults ( ( previousResult , dp ) => { linkTypeIds = linkTypeIds . filter ( id => ! previousResult || previousResult . map ( lt => lt . id ) . indexOf ( id ) === - 1 ) ; return linkTypeIds . length > 0 ? dp . dataProvider . linkTypesInfo ( { linkTypeIds : linkTypeIds } ) : undefined ; } ) . then ( mergeLinkTypesInfo ) ; } } linkTypes ( ) { return this . fetchSequentially ( 'linkTypes' , mergeLinkTypes , undefined ) ; } elementInfo ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'elementInfo' , mergeElementInfo , params ) ; } else { let elementIds = params . elementIds ; return this . queueProcessResults ( ( previousResult , dp ) => { elementIds = elementIds . filter ( id => ! previousResult || ! previousResult [ id ] ) ; return elementIds . length > 0 ? dp . dataProvider . elementInfo ( { elementIds : elementIds } ) : undefined ; } ) . then ( mergeElementInfo ) ; } } linksInfo ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'linksInfo' , mergeLinksInfo , params ) ; } else { let elementIds = params . elementIds ; return this . queueProcessResults ( ( previousResult , dp ) => { elementIds = elementIds . filter ( id => { if ( previousResult ) { for ( const linkModel of previousResult ) { if ( linkModel . sourceId === id ) { return false ; } } } return true ; } ) ; return elementIds . length > 0 ? dp . dataProvider . linksInfo ( { elementIds : elementIds , linkTypeIds : params . linkTypeIds } ) : undefined ; } ) . then ( mergeLinksInfo ) ; } } linkTypesOf ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'linkTypesOf' , mergeLinkTypesOf , params ) ; } else { return this . queueProcessResults ( ( previousResult , dp ) => { if ( ! previousResult || previousResult && previousResult . length === 0 ) { return dp . dataProvider . linkTypesOf ( params ) ; } else { return undefined ; } } ) . then ( mergeLinkTypesOf ) ; } } linkElements ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'linkElements' , mergeLinkElements , params ) ; } else { return this . queueProcessResults ( ( previousResult , dp ) => { if ( ! previousResult || previousResult && Object . keys ( previousResult ) . length === 0 ) { return dp . dataProvider . linkElements ( params ) ; } else { return undefined ; } } ) . then ( mergeLinkElements ) ; } } filter ( params ) { if ( this . mergeMode === 'fetchAll' ) { return this . fetchSequentially ( 'filter' , mergeFilter , params ) ; } else { return this . queueProcessResults ( ( previousResult , dp ) => { if ( ! previousResult || previousResult && Object . keys ( previousResult ) . length === 0 ) { return dp . dataProvider . filter ( params ) ; } else { return undefined ; } } ) . then ( mergeFilter ) ; } } private processResults < ResponseType > ( responsePromise , dpName , useProviderInStats ? ) { return responsePromise . then ( response => ( { dataSourceName : dpName , useInStats : useProviderInStats , response : response } ) ) . catch ( error => { console . error ( error ) ; return { dataSourceName : dpName , useInStats : useProviderInStats , response : undefined } ; } ) ; } private queueProcessResults < ResponseType > ( callBack ) { let counter = 0 ; const responseList = [ ] ; const recursiveCall = ( result ? ) : Promise < CompositeResponse < ResponseType > [ ] > => { if ( this . dataProviders . length > counter ) { const dp = this . dataProviders [ counter ++ ] ; const callBackResult = callBack ( result , dp ) ; if ( ! callBackResult ) { return Promise . resolve ( responseList ) ; } return callBackResult . then ( newResult => { responseList . push ( { dataSourceName : dp . name , response : newResult , } ) ; return recursiveCall ( newResult ) ; } ) . catch ( error => { console . error ( error ) ; return recursiveCall ( result ) ; } ) ; } else { return Promise . resolve ( responseList ) ; } } ; return recursiveCall ( ) ; } private fetchSequentially < K extends keyof DataProvider > ( functionName , mergeFunction , params ) { const resultPromises = this . dataProviders . map ( ( dp ) => { const providerMethod = dp . dataProvider [ functionName ] as any as ( this : DataProvider , params : OperationParams < K > ) => Promise < OperationResult < K > > ; return this . processResults ( providerMethod . call ( dp . dataProvider , params ) , dp . name , dp . useInStats ) ; } ) ; return Promise . all ( resultPromises ) . then ( mergeFunction ) ; } } type OperationParams < K extends keyof DataProvider > = DataProvider [ K ] extends ( params : infer P ) => any ? P : never ; type OperationResult < K extends keyof DataProvider > = ReturnType < DataProvider [ K ] > extends Promise < infer R > ? R : never ;