UNPKG

graph-explorer

Version:

Graph Explorer can be used to explore and RDF graphs in SPARQL endpoints or on the web.

340 lines (317 loc) 9.97 kB
import { DataProvider, LinkElementsParams, FilterParams } from "../provider"; import { Dictionary, ClassModel, LinkType, ElementModel, LinkModel, LinkCount, PropertyModel, 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: DataProvider | DPDefinition): dp is DPDefinition { 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: (DataProvider | DPDefinition)[], params?: { mergeMode?: MergeMode; } ) { 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(): Promise<ClassModel[]> { return this.fetchSequentially("classTree", mergeClassTree, undefined); } propertyInfo(params: { propertyIds: PropertyTypeIri[]; }): Promise<Dictionary<PropertyModel>> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially("propertyInfo", mergePropertyInfo, params); } else { let propertyIds = params.propertyIds; return this.queueProcessResults( (previousResult: Dictionary<PropertyModel>, dp: DPDefinition) => { propertyIds = propertyIds.filter( (id) => !previousResult || !previousResult[id] ); return propertyIds.length > 0 ? dp.dataProvider.propertyInfo({ propertyIds: propertyIds }) : undefined; } ).then(mergePropertyInfo); } } classInfo(params: { classIds: ElementTypeIri[] }): Promise<ClassModel[]> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially("classInfo", mergeClassInfo, params); } else { let classIds = params.classIds; return this.queueProcessResults( (previousResult: ClassModel[], dp: DPDefinition) => { 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: { linkTypeIds: LinkTypeIri[] }): Promise<LinkType[]> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially( "linkTypesInfo", mergeLinkTypesInfo, params ); } else { let linkTypeIds = params.linkTypeIds; return this.queueProcessResults( (previousResult: LinkType[], dp: DPDefinition) => { 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(): Promise<LinkType[]> { return this.fetchSequentially("linkTypes", mergeLinkTypes, undefined); } elementInfo(params: { elementIds: ElementIri[]; }): Promise<Dictionary<ElementModel>> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially("elementInfo", mergeElementInfo, params); } else { let elementIds = params.elementIds; return this.queueProcessResults( (previousResult: Dictionary<ElementModel>, dp: DPDefinition) => { elementIds = elementIds.filter( (id) => !previousResult || !previousResult[id] ); return elementIds.length > 0 ? dp.dataProvider.elementInfo({ elementIds: elementIds }) : undefined; } ).then(mergeElementInfo); } } linksInfo(params: { elementIds: ElementIri[]; linkTypeIds: LinkTypeIri[]; }): Promise<LinkModel[]> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially("linksInfo", mergeLinksInfo, params); } else { let elementIds = params.elementIds; return this.queueProcessResults( (previousResult: LinkModel[], dp: DPDefinition) => { 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: { elementId: ElementIri }): Promise<LinkCount[]> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially("linkTypesOf", mergeLinkTypesOf, params); } else { return this.queueProcessResults( (previousResult: LinkCount[], dp: DPDefinition) => { if ( !previousResult || (previousResult && previousResult.length === 0) ) { return dp.dataProvider.linkTypesOf(params); } else { return undefined; } } ).then(mergeLinkTypesOf); } } linkElements(params: LinkElementsParams): Promise<Dictionary<ElementModel>> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially("linkElements", mergeLinkElements, params); } else { return this.queueProcessResults( (previousResult: Dictionary<ElementModel>, dp: DPDefinition) => { if ( !previousResult || (previousResult && Object.keys(previousResult).length === 0) ) { return dp.dataProvider.linkElements(params); } else { return undefined; } } ).then(mergeLinkElements); } } filter(params: FilterParams): Promise<Dictionary<ElementModel>> { if (this.mergeMode === "fetchAll") { return this.fetchSequentially("filter", mergeFilter, params); } else { return this.queueProcessResults( (previousResult: Dictionary<ElementModel>, dp: DPDefinition) => { if ( !previousResult || (previousResult && Object.keys(previousResult).length === 0) ) { return dp.dataProvider.filter(params); } else { return undefined; } } ).then(mergeFilter); } } private processResults<ResponseType>( responsePromise: Promise<ResponseType>, dpName: string, useProviderInStats?: boolean ): Promise<CompositeResponse<ResponseType>> { return responsePromise .then((response) => ({ dataSourceName: dpName, useInStats: useProviderInStats, response: response, })) .catch((error) => { console.error(error); return { dataSourceName: dpName, useInStats: useProviderInStats, response: undefined as any, }; }); } private queueProcessResults<ResponseType>( callBack: ( previousResult: ResponseType, dp: DPDefinition ) => Promise<ResponseType> ): Promise<CompositeResponse<ResponseType>[]> { let counter = 0; const responseList: CompositeResponse<ResponseType>[] = []; const recursiveCall = ( result?: ResponseType ): 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: K, mergeFunction: ( response: CompositeResponse<OperationResult<K>>[] ) => OperationResult<K>, params: OperationParams<K> ) { const resultPromises = this.dataProviders.map((dp: DPDefinition) => { 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;