import { Node , RDFGraph , Triple } from 'rdf-ext' ; import { RDFCacheableStore , MatchStatement , prefixFactory , isLiteral , isNamedNode , isLabelType , } from './rdfCacheableStore' ; import { DataProvider , LinkElementsParams , FilterParams } from '../provider' ; import { RDFLoader } from './rdfLoader' ; import { LocalizedString , Dictionary , ClassModel , LinkType , ElementModel , LinkModel , LinkCount , PropertyModel , Property , ElementIri , ElementTypeIri , LinkTypeIri , PropertyTypeIri , } from '../model' ; import { RDFCompositeParser } from './rdfCompositeParser' ; const PREFIX_FACTORIES = { RDF : prefixFactory ( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' ) , RDFS : prefixFactory ( 'http://www.w3.org/2000/01/rdf-schema#' ) , FOAF : prefixFactory ( 'http://xmlns.com/foaf/0.1/' ) , XSD : prefixFactory ( 'http://www.w3.org/2001/XMLSchema#' ) , OWL : prefixFactory ( 'http://www.w3.org/2002/07/owl#' ) , } ; export interface RDFFile { content : string ; fileName ? : string ; type ? : string ; uri ? : string ; } export interface RDFDataProviderOptions { data : RDFFile [ ] ; parsers : { [ id ] : any ; } ; acceptBlankNodes ? : boolean ; dataFetching ? : boolean ; proxy ? : string ; } /** An opaque reference to RDFGraph type from `rdf-ext` library */ export type RDFExtGraph = object ; export class RDFDataProvider implements DataProvider { public dataFetching : boolean ; private initStatement : Promise < any > | undefined ; private rdfStorage : RDFCacheableStore ; private rdfLoader : RDFLoader ; readonly options : RDFDataProviderOptions ; constructor ( options ) { const parser = new RDFCompositeParser ( options . parsers ) ; this . rdfStorage = new RDFCacheableStore ( { parser , acceptBlankNodes : options . acceptBlankNodes , } ) ; this . rdfLoader = new RDFLoader ( { parser : parser , proxy : options . proxy , } ) ; this . dataFetching = options . dataFetching ; const parsePromises = ( options . data || [ ] ) . map ( ( datum ) => { return parser . parse ( datum . content , datum . type , datum . fileName ) . then ( ( rdfGraph ) => { this . rdfStorage . add ( rdfGraph ) ; return true ; } ) ; } ) ; this . initStatement = Promise . all ( parsePromises ) . catch ( ( error ) => { error . message = 'Initialization failed! Cause: ' + error . message ; throw error ; } ) ; this . options = options ; } addGraph ( graph ) { this . rdfStorage . add ( graph as RDFGraph ) ; } private waitInitCompleted ( ) { if ( this . initStatement ) { return this . initStatement . then ( ( ) => { delete this . initStatement ; } ) ; } else { return Promise . resolve ( ) ; } } async classTree ( ) { await this . waitInitCompleted ( ) ; const rdfClassesQuery = this . rdfStorage . match ( null , PREFIX_FACTORIES . RDF ( 'type' ) , PREFIX_FACTORIES . RDFS ( 'Class' ) , null ) ; const owlClassesQuery = this . rdfStorage . match ( null , PREFIX_FACTORIES . RDF ( 'type' ) , PREFIX_FACTORIES . OWL ( 'Class' ) ) ; const fromRDFTypesQuery = this . rdfStorage . match ( null , PREFIX_FACTORIES . RDF ( 'type' ) , null ) ; const subClassesQuery = this . rdfStorage . match ( null , PREFIX_FACTORIES . RDFS ( 'subClassOf' ) , null ) ; const [ rdfClassesGraph , owlClassesGraph , rdfTypesGraph , subClassesGraph , ] = await Promise . all ( [ rdfClassesQuery , owlClassesQuery , fromRDFTypesQuery , subClassesQuery , ] ) ; const rdfClasses = rdfClassesGraph . toArray ( ) . map ( ( cl ) => cl . subject ) ; const owlClasses = owlClassesGraph . toArray ( ) . map ( ( cl ) => cl . subject ) ; const rdfTypes = rdfTypesGraph . toArray ( ) . map ( ( cl ) => cl . object ) ; const subClasses = subClassesGraph . toArray ( ) ; const classes = rdfClasses . concat ( owlClasses ) . concat ( rdfTypes ) ; const classIris = classes . filter ( ( clazz ) => clazz . interfaceName !== 'BlankNode' ) . map ( ( clazz ) => clazz . nominalValue as ElementTypeIri ) ; const parents = { } ; for ( const triple of subClasses ) { const subClassIRI = triple . subject . nominalValue as ElementTypeIri ; const classIRI = triple . object . nominalValue as ElementTypeIri ; if ( isNamedNode ( triple . subject ) && ! parents [ subClassIRI ] ) { parents [ subClassIRI ] = [ ] ; } if ( isNamedNode ( triple . object ) && parents [ subClassIRI ] . indexOf ( classIRI ) === - 1 ) { parents [ subClassIRI ] . push ( classIRI ) ; } } const dictionary = { } ; const firstLevel = { } ; const labelQueries = [ ] ; for ( const classIri of classIris ) { let classElement ; if ( ! dictionary [ classIri ] ) { classElement = this . createEmptyClass ( classIri ) ; dictionary [ classIri ] = classElement ; firstLevel [ classIri ] = classElement ; labelQueries . push ( this . getLabels ( classIri ) . then ( ( labels ) => { classElement . label = { values : labels } ; } ) ) ; } else { classElement = dictionary [ classIri ] ; } if ( parents [ classIri ] ) { for ( const parentIri of parents [ classIri ] ) { if ( ! dictionary [ parentIri ] ) { const parentElement = this . createEmptyClass ( parentIri ) ; dictionary [ parentIri ] = parentElement ; firstLevel [ parentIri ] = parentElement ; labelQueries . push ( this . getLabels ( parentIri ) . then ( ( labels ) => { parentElement . label = { values : labels } ; } ) ) ; } if ( dictionary [ parentIri ] . children . indexOf ( classElement ) === - 1 ) { dictionary [ parentIri ] . children . push ( classElement ) ; dictionary [ parentIri ] . count += classElement . count ; } delete firstLevel [ classElement . id ] ; } } } await Promise . all ( labelQueries ) ; const result = Object . keys ( firstLevel ) . map ( ( k ) => firstLevel [ k ] ) ; return result ; } async propertyInfo ( params ) { await this . waitInitCompleted ( ) ; const propertyInfoResult = { } ; const queries = params . propertyIds . map ( async ( propId ) => { try { const isExists = await this . checkElement ( propId ) ; await this . fetchIfNecessary ( propId , isExists ) ; const labels = await this . getLabels ( propId ) ; return { id : propId , label : { values : labels } , } ; } catch ( error ) { console . warn ( error ) ; return null ; } } ) ; const fetchedModels = await Promise . all ( queries ) ; for ( const model of fetchedModels ) { if ( model ) { propertyInfoResult [ model . id ] = model ; } } return propertyInfoResult ; } async classInfo ( params ) { await this . waitInitCompleted ( ) ; const queries = params . classIds . map ( async ( classId ) => { try { const isExists = await this . checkElement ( classId ) ; await this . fetchIfNecessary ( classId , isExists ) ; const labels = await this . getLabels ( classId ) ; return { id : classId , label : { values : labels } , count : this . rdfStorage . getTypeCount ( classId ) , children : [ ] , } ; } catch ( error ) { console . warn ( error ) ; return null ; } } ) ; const fetchedModels = await Promise . all ( queries ) ; return fetchedModels . filter ( ( cm ) => cm ) ; } async linkTypesInfo ( params ) { await this . waitInitCompleted ( ) ; const queries = params . linkTypeIds . map ( async ( typeId ) => { try { const isExists = await this . rdfStorage . checkElement ( typeId ) ; await this . fetchIfNecessary ( typeId , isExists ) ; const labels = await this . getLabels ( typeId ) ; return { id : typeId , label : { values : labels } , count : this . rdfStorage . getTypeCount ( typeId ) , } ; } catch ( error ) { console . warn ( error ) ; return null ; } } ) ; const fetchedModels = await Promise . all ( queries ) ; return fetchedModels . filter ( ( lt ) => lt ) ; } async linkTypes ( ) { await this . waitInitCompleted ( ) ; const linkTypes = [ ] ; const rdfLinksQueries = this . rdfStorage . match ( undefined , PREFIX_FACTORIES . RDF ( 'type' ) , PREFIX_FACTORIES . RDF ( 'Property' ) ) ; const owlLinksQueries = this . rdfStorage . match ( undefined , PREFIX_FACTORIES . RDF ( 'type' ) , PREFIX_FACTORIES . OWL ( 'ObjectProperty' ) ) ; const [ rdfLinks , owlLinks ] = await Promise . all ( [ rdfLinksQueries , owlLinksQueries , ] ) ; const linkTypeIds = new Set ( ) ; const mapLinkTriple = async ( triple ) : Promise < void > => { if ( ! linkTypeIds . has ( triple . subject . nominalValue ) ) { linkTypeIds . add ( triple . subject . nominalValue ) ; const labels = await this . getLabels ( triple . subject . nominalValue ) ; linkTypes . push ( { id : triple . subject . nominalValue as LinkTypeIri , label : { values : labels } , count : this . rdfStorage . getTypeCount ( triple . subject . nominalValue ) , } ) ; } } ; const linkTypePromises = [ ] ; for ( const linkTriple of rdfLinks . toArray ( ) ) { linkTypePromises . push ( mapLinkTriple ( linkTriple ) ) ; } for ( const linkTriple of owlLinks . toArray ( ) ) { linkTypePromises . push ( mapLinkTriple ( linkTriple ) ) ; } await Promise . all ( linkTypePromises ) ; return linkTypes ; } async elementInfo ( params ) { await this . waitInitCompleted ( ) ; const elementInfoResult = { } ; const queries = params . elementIds . map ( async ( elementId ) => { try { const isExists = await this . rdfStorage . checkElement ( elementId ) ; await this . fetchIfNecessary ( elementId , isExists ) ; return this . getElementInfo ( elementId ) ; } catch ( error ) { console . warn ( error ) ; return null ; } } ) ; const fetchedModels = await Promise . all ( queries ) ; for ( const model of fetchedModels ) { if ( model ) { elementInfoResult [ model . id ] = model ; } } return elementInfoResult ; } async linksInfo ( params ) { await this . waitInitCompleted ( ) ; const statementPromises = [ ] ; for ( const source of params . elementIds ) { for ( const target of params . elementIds ) { statementPromises . push ( Promise . all ( [ this . rdfStorage . checkElement ( source ) . then ( ( exists ) => this . fetchIfNecessary ( source , exists ) ) , this . rdfStorage . checkElement ( target ) . then ( ( exists ) => this . fetchIfNecessary ( target , exists ) ) , ] ) . then ( ( ) => ( { subject : source , object : target } ) ) . catch ( ( error ) => { console . warn ( error ) ; return null ; } ) ) ; } } const statements = await Promise . all ( statementPromises ) ; const graph = await this . rdfStorage . matchAll ( statements . filter ( ( statement ) => statement ) ) ; let triples ; if ( this . options . acceptBlankNodes ) { triples = graph . toArray ( ) ; } else { triples = graph . toArray ( ) . filter ( ( tripple ) => isNamedNode ( tripple . subject ) && isNamedNode ( tripple . object ) ) ; } const fetchedModels = triples . map ( ( t ) : LinkModel => ( { linkTypeId : t . predicate . nominalValue as LinkTypeIri , sourceId : t . subject . nominalValue as ElementIri , targetId : t . object . nominalValue as ElementIri , } ) ) ; return fetchedModels ; } async linkTypesOf ( params ) { await this . waitInitCompleted ( ) ; const links = [ ] ; const element = params . elementId ; const linkMap = { } ; const incomingElementsTriples = await this . rdfStorage . match ( null , null , element ) ; const incomingElements = incomingElementsTriples . toArray ( ) . filter ( ( t ) => isNamedNode ( t . subject ) ) . map ( ( triple ) => triple . predicate ) ; for ( const el of incomingElements ) { const linkTypeId = el . nominalValue as LinkTypeIri ; if ( ! linkMap [ linkTypeId ] ) { linkMap [ linkTypeId ] = { id : linkTypeId , inCount : 1 , outCount : 0 , } ; links . push ( linkMap [ linkTypeId ] ) ; } else { linkMap [ linkTypeId ] . inCount ++ ; } } const outgoingElementsTriples = await this . rdfStorage . match ( element , null , null ) ; const outElements = outgoingElementsTriples . toArray ( ) . filter ( ( t ) => isNamedNode ( t . object ) ) . map ( ( triple ) => triple . predicate ) ; for ( const el of outElements ) { const linkTypeId = el . nominalValue as LinkTypeIri ; if ( ! linkMap [ linkTypeId ] ) { linkMap [ linkTypeId ] = { id : linkTypeId , inCount : 0 , outCount : 1 , } ; links . push ( linkMap [ linkTypeId ] ) ; } else { linkMap [ linkTypeId ] . outCount ++ ; } } return links ; } async linkElements ( params ) { await this . waitInitCompleted ( ) ; return await this . filter ( { refElementId : params . elementId , refElementLinkId : params . linkId , linkDirection : params . direction , limit : params . limit , offset : params . offset , languageCode : '' , } ) ; } async filter ( params ) { await this . waitInitCompleted ( ) ; if ( params . limit === undefined ) { params . limit = 100 ; } const limitCallback = ( node , index ) => { return ( ( blankNodes || isNamedNode ( node ) ) && offsetIndex <= index && index < limitIndex ) ; } ; const offsetIndex = params . offset ; const limitIndex = params . offset + params . limit ; const blankNodes = this . options . acceptBlankNodes ; let elementsPromise ; if ( params . elementTypeId ) { elementsPromise = this . filterByTypeId ( params . elementTypeId , limitCallback ) ; } else if ( params . refElementId && params . refElementLinkId ) { elementsPromise = this . filterByRefAndLink ( params . refElementId , params . refElementLinkId , params . linkDirection , limitCallback ) ; } else if ( params . refElementId ) { elementsPromise = this . filterByRef ( params . refElementId , limitCallback ) ; } else if ( params . text ) { const filteredElements = await this . getAllElements ( params . text , limitCallback ) ; return this . filterByKey ( undefined , filteredElements ) ; } else { return { } ; } const elements = await elementsPromise ; return this . filterByKey ( params . text , elements ) ; } private async filterByTypeId ( elementTypeId , filter ) { const elementTriples = await this . rdfStorage . match ( undefined , PREFIX_FACTORIES . RDF ( 'type' ) , elementTypeId ) ; return Promise . all ( elementTriples . toArray ( ) . filter ( ( t , index ) => filter ( t . subject , index ) ) . map ( ( el ) => this . getElementInfo ( el . subject . nominalValue as ElementIri , true ) ) ) ; } private async filterByRefAndLink ( refEl , refLink , linkDirection , filter ) { if ( linkDirection === 'in' ) { const elementTriples = await this . rdfStorage . match ( null , refLink , refEl ) ; return Promise . all ( elementTriples . toArray ( ) . filter ( ( t , index ) => filter ( t . subject , index ) ) . map ( ( el ) => this . getElementInfo ( el . subject . nominalValue as ElementIri , true ) ) ) ; } else { const elementTriples = await this . rdfStorage . match ( refEl , refLink , null ) ; return Promise . all ( elementTriples . toArray ( ) . filter ( ( t , index ) => filter ( t . object , index ) ) . map ( ( el ) => this . getElementInfo ( el . object . nominalValue as ElementIri , true ) ) ) ; } } private async filterByRef ( refEl , filter ) { const incomingTriples = await this . rdfStorage . match ( null , null , refEl ) ; const inRelations = await Promise . all ( incomingTriples . toArray ( ) . filter ( ( t , index ) => filter ( t . subject , index ) ) . map ( ( el ) => this . getElementInfo ( el . subject . nominalValue as ElementIri , true ) ) ) ; const outgoingTriples = await this . rdfStorage . match ( refEl , null , null ) ; const outRelations = await Promise . all ( outgoingTriples . toArray ( ) . filter ( ( t , index ) => filter ( t . object , index ) ) . map ( ( el ) => this . getElementInfo ( el . object . nominalValue as ElementIri , true ) ) ) ; return inRelations . concat ( outRelations ) ; } private async getAllElements ( text , filter ) { const key = text ? text . toLowerCase ( ) : null ; const elementTriples = await this . rdfStorage . match ( null , null , null ) ; const promices = [ ] ; for ( const triple of elementTriples . toArray ( ) ) { if ( filter ( triple . subject , promices . length ) ) { const objectIsLiteral = triple . object . interfaceName === 'Literal' ; const isLabel = isLabelType ( triple . predicate . nominalValue ) ; const containsKey = ( isLabel && triple . object . nominalValue . toLowerCase ( ) . indexOf ( key ) !== - 1 ) || ( ! objectIsLiteral && triple . subject . nominalValue . toLowerCase ( ) . indexOf ( key ) !== - 1 ) ; if ( containsKey ) { promices . push ( this . getElementInfo ( triple . subject . nominalValue as ElementIri , true ) ) ; } } } return Promise . all ( promices ) ; } private filterByKey ( text , elements ) { const result = { } ; const key = text ? text . toLowerCase ( ) : null ; if ( key ) { for ( const el of elements ) { let acceptableKey = false ; for ( const label of el . label . values ) { acceptableKey = acceptableKey || label . value . toLowerCase ( ) . indexOf ( key ) !== - 1 ; if ( acceptableKey ) { break ; } } acceptableKey = acceptableKey || el . id . toLowerCase ( ) . indexOf ( key ) !== - 1 ; if ( acceptableKey ) { result [ el . id ] = el ; } } } else { for ( const el of elements ) { result [ el . id ] = el ; } } return result ; } private checkElement ( id ) { return this . dataFetching ? this . rdfStorage . checkElement ( id ) : Promise . resolve ( true ) ; } private fetchIfNecessary ( id , exists ) { if ( exists || ! this . dataFetching ) { return Promise . resolve ( ) ; } else { return this . rdfLoader . downloadElement ( id ) . then ( ( rdfGraph ) => { this . rdfStorage . add ( rdfGraph ) ; return ; } ) . catch ( ( error ) => { console . warn ( error ) ; return ; } ) ; } } private getElementInfo ( id , shortInfo ? ) { return Promise . all ( [ this . getTypes ( id ) , ! shortInfo ? this . getProps ( id ) : Promise . resolve ( { } ) , this . getLabels ( id ) , ] ) . then ( ( [ types , props , labels ] ) => { return { id : id , types : types , label : { values : labels } , properties : props , } ; } ) ; } private getLabels ( id ) { return this . rdfStorage . getLabels ( id ) . then ( ( labelTriples ) => { return labelTriples . toArray ( ) . map ( ( l ) : LocalizedString => ( { value : l . object . nominalValue , language : isLiteral ( l . object ) ? l . object . language || '' : '' , } ) ) ; } ) ; } private getProps ( el ) { return this . rdfStorage . match ( el , null , null ) . then ( ( propsGraph ) => { const props = { } ; const propTriples = propsGraph . toArray ( ) ; for ( const statemet of propTriples ) { if ( isLiteral ( statemet . object ) && statemet . predicate . nominalValue !== PREFIX_FACTORIES . RDFS ( 'label' ) ) { const property = props [ statemet . predicate . nominalValue ] ; const value = { value : statemet . object . nominalValue , language : statemet . object . language || '' , } ; props [ statemet . predicate . nominalValue ] = { type : 'string' , values : property ? [ ... property . values , value ] : [ value ] , } as Property ; } } return props ; } ) ; } private getTypes ( el ) { return this . rdfStorage . match ( el , PREFIX_FACTORIES . RDF ( 'type' ) , null ) . then ( ( typeTriples ) => { return typeTriples . toArray ( ) . map ( ( t ) => t . object . nominalValue as ElementTypeIri ) ; } ) ; } private createEmptyClass ( classIri ) { return { id : classIri , label : { values : [ ] , } , count : this . rdfStorage . getTypeCount ( classIri ) , children : [ ] , } ; } }