sparnatural
Version:
Visual client-side SPARQL query builder and knowledge graph exploration tool
221 lines (186 loc) • 6.79 kB
text/typescript
import LocalCacheData from "../../../datastorage/LocalCacheData";
import { Catalog } from "../../../settings/Catalog";
import { UrlFetcher } from "./UrlFetcher";
export interface SparqlHandlerIfc {
executeSparql(
sparql:string,
callback: (data: any) => void,
errorCallback?:(error: any) => void
):void;
}
export class SparqlHandlerFactory {
protected lang:string;
protected localCacheDataTtl:any;
protected extraHeaders:Map<string,string>;
protected customizedSparqlHandler:SparqlHandlerIfc;
protected catalog?:Catalog;
constructor(
lang:string,
localCacheDataTtl:any,
extraHeaders:Map<string,string>,
customizedSparqlHandler?:SparqlHandlerIfc,
catalog?:Catalog
) {
this.lang = lang;
this.localCacheDataTtl = localCacheDataTtl;
this.extraHeaders = extraHeaders;
this.customizedSparqlHandler = customizedSparqlHandler;
this.catalog = catalog;
}
buildSparqlHandler(endpoints:string[]):SparqlHandlerIfc {
// if customized handler, use it
if(this.customizedSparqlHandler) {
return this.customizedSparqlHandler;
}
// if more than one endpoint
if(endpoints.length > 1) {
// extract selected endpoints from full catalog
let subCatalog = this.catalog.extractSubCatalog(endpoints);
return new MultipleEndpointSparqlHandler(
new UrlFetcher(this.localCacheDataTtl, this.extraHeaders),
subCatalog,
this.lang
);
} else {
// only one single endpoint
let endpoint:string = endpoints[0]
return new EndpointSparqlHandler(new UrlFetcher(this.localCacheDataTtl, this.extraHeaders), endpoint);
}
}
}
/**
* Executes a SPARQL query against a remote endpoint at a known URL
*/
export class EndpointSparqlHandler implements SparqlHandlerIfc {
urlFetcher:UrlFetcher;
sparqlEndpointUrl: any;
constructor(
urlFetcher:UrlFetcher,
sparqlEndpointUrl: any
) {
this.urlFetcher = urlFetcher,
this.sparqlEndpointUrl = sparqlEndpointUrl;
}
buildUrl(sparql:string):string {
var separator = this.sparqlEndpointUrl.indexOf("?") > 0 ? "&" : "?";
var url =
this.sparqlEndpointUrl +
separator +
this.buildParameters(sparql)
return url;
}
buildParameters(sparql:string):string {
return "query=" + encodeURIComponent(sparql) + "&format=json";
}
executeSparqlPost(
sparql:string,
callback: (data: {}) => void,
errorCallback?:(error: any) => void
):void {
let url = this.sparqlEndpointUrl;
const headers = new Headers();
headers.append("Content-Type", "application/x-www-form-urlencoded");
headers.append("Accept", "application/sparql-results+json,*/*;q=0.9");
return this.urlFetcher.fetchUrlWithParameters(
url,
{
method:"POST",
body:this.buildParameters(sparql),
headers: headers
},
callback,
errorCallback
);
}
executeSparqlGet(
sparql:string,
callback: (data: {}) => void,
errorCallback?:(error: any) => void
):void {
let url = this.buildUrl(sparql);
this.urlFetcher.fetchUrl(
url,
callback,
errorCallback
);
}
executeSparql(
sparql:string,
callback: (data: {}) => void,
errorCallback?:(error: any) => void
):void {
if(sparql.length < 1024)
this.executeSparqlGet(sparql,callback,errorCallback);
else
this.executeSparqlPost(sparql,callback,errorCallback);
}
}
export class MultipleEndpointSparqlHandler implements SparqlHandlerIfc {
urlFetcher:UrlFetcher;
catalog: Catalog;
addExtraEndpointColumn:boolean;
// name of the extra column to add in the result set to express the endpoint
extraColumnName = "group";
lang:string;
constructor(
urlFetcher:UrlFetcher,
catalog: Catalog,
lang:string
) {
this.urlFetcher = urlFetcher,
this.catalog = catalog;
this.addExtraEndpointColumn = true;
this.lang = lang;
}
executeSparql(
sparql:string,
callback: (data: any) => void,
errorCallback?:(error: any) => void
):void {
const promises:Promise<{}>[] = [];
for(const i in this.catalog.getServices()) {
console.log("Calling "+this.catalog.getServices()[i].getEndpointURL())
let fetcher = new EndpointSparqlHandler(this.urlFetcher, this.catalog.getServices()[i].getEndpointURL());
promises[promises.length] = new Promise((resolve, reject) => {
fetcher.executeSparql(
sparql,
(data: any) =>{resolve({ endpoint: this.catalog.getServices()[i], sparqlResult: data })},
(error: any)=>{reject(error)}
)
});
}
// then wait for all Promises
Promise.all(promises).then((values:any[]) => {
let finalResult:any = {};
// copy the same head as first result, with an extra "endpoint" column
finalResult.head = values[0].sparqlResult.head;
finalResult.head.vars.push(this.extraColumnName);
// prepare the "results" section
finalResult.results = {
// same distinct as first result
distinct: values[0].sparqlResult.results.distinct,
// never ordered
ordered: false,
// prepare bindings section
bindings: []
};
// then for each SPARQL results of structure {endpoint : xx, sparqlJson: {...}}
for (const v of values) {
// add an extra "endpoint" column with the endpoint at the end of each binding
finalResult.results.bindings.push(
// remap each binding to add the endpoint column at the end
// then unpack the array
...v.sparqlResult.results.bindings.map((b: { [x: string]: { type: string; value: any; }; }) => {
if(this.addExtraEndpointColumn) {
b[this.extraColumnName] = {type: "literal", value:v.endpoint.getTitle(this.lang)};
}
return b;
})
);
}
// TODO : handle errors
// and then call the callback
callback(finalResult);
});
}
}