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
text/typescript
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;