origintrail-node
Version:
OriginTrail Node - Decentralized Knowledge Graph Node Library
240 lines (205 loc) • 9.23 kB
JavaScript
import axios from 'axios';
import OtTripleStore from '../ot-triple-store.js';
import { MEDIA_TYPES } from '../../../../constants/constants.js';
class OtBlazegraph extends OtTripleStore {
async initialize(config, logger) {
await super.initialize(config, logger);
// this regex will match \Uxxxxxxxx but will exclude cases where there is a double slash before U (\\U)
this.unicodeRegex = /(?<!\\)\\U([a-fA-F0-9]{8})/g;
await Promise.all(
Object.keys(this.repositories).map(async (repository) => {
await this.createRepository(repository);
}),
);
}
async createRepository(repository) {
const { url, name } = this.repositories[repository];
if (!(await this.repositoryExists(repository))) {
await axios.post(
`${url}/blazegraph/namespace`,
`com.bigdata.rdf.sail.truthMaintenance=false\n` +
`com.bigdata.namespace.${name}.spo.com.bigdata.btree.BTree.branchingFactor=1024\n` +
`com.bigdata.rdf.store.AbstractTripleStore.textIndex=false\n` +
`com.bigdata.rdf.store.AbstractTripleStore.justify=false\n` +
`com.bigdata.rdf.store.AbstractTripleStore.statementIdentifiers=false\n` +
`com.bigdata.rdf.store.AbstractTripleStore.axiomsClass=com.bigdata.rdf.axioms.NoAxioms\n` +
`com.bigdata.rdf.sail.namespace=${name}\n` +
`com.bigdata.rdf.store.AbstractTripleStore.quads=true\n` +
`com.bigdata.namespace.${name}.lex.com.bigdata.btree.BTree.branchingFactor=400\n` +
`com.bigdata.rdf.store.AbstractTripleStore.geoSpatial=false\n` +
`com.bigdata.journal.Journal.groupCommit=false\n` +
`com.bigdata.rdf.sail.isolatableIndices=false\n` +
`com.bigdata.rdf.store.AbstractTripleStore.enableRawRecordsSupport=false\n` +
`com.bigdata.rdf.store.AbstractTripleStore.Options.inlineTextLiterals=true\n` +
`com.bigdata.rdf.store.AbstractTripleStore.Options.maxInlineTextLength=128\n` +
`com.bigdata.rdf.store.AbstractTripleStore.Options.blobsThreshold=256\n`,
{
headers: {
'Content-Type': 'text/plain',
},
},
);
}
}
initializeSparqlEndpoints(repository) {
const { url, name } = this.repositories[repository];
this.repositories[repository].sparqlEndpoint = `${url}/blazegraph/namespace/${name}/sparql`;
this.repositories[
repository
].sparqlEndpointUpdate = `${url}/blazegraph/namespace/${name}/sparql`;
}
getRepositoryUrl(repository) {
return this.repositories[repository].url;
}
hasUnicodeCodePoints(input) {
return this.unicodeRegex.test(input);
}
decodeUnicodeCodePoints(input) {
const decodedString = input.replace(this.unicodeRegex, (match, hex) => {
const codePoint = parseInt(hex, 16);
return String.fromCodePoint(codePoint);
});
return decodedString;
}
utfConverter(input) {
return Buffer.from(input, 'utf8').toString();
}
async construct(repository, query, timeout) {
return this._executeQuery(repository, query, MEDIA_TYPES.N_QUADS, timeout);
}
async select(repository, query, timeout) {
const result = await this._executeQuery(repository, query, MEDIA_TYPES.JSON, timeout);
return result ? JSON.parse(result) : [];
}
async ask(repository, query, timeout = 10000) {
const result = await this._executeQuery(repository, query, MEDIA_TYPES.JSON, timeout);
return result ? JSON.parse(result).boolean : false;
}
async _executeQuery(repository, query, mediaType, timeout) {
const result = await axios.post(this.repositories[repository].sparqlEndpoint, query, {
headers: {
'Content-Type': 'application/sparql-query',
'X-BIGDATA-MAX-QUERY-MILLIS': timeout,
Accept: mediaType,
},
});
let response;
if (mediaType === MEDIA_TYPES.JSON) {
// Check if this is an ASK query by looking for the boolean property
if (result.data.boolean !== undefined) {
// This is an ASK query response
response = JSON.stringify(result.data);
} else {
// This is a SELECT query response
const { bindings } = result.data.results;
let output = '[\n';
bindings.forEach((binding, bindingIndex) => {
let string = ' {\n';
const keys = Object.keys(binding);
keys.forEach((key, index) => {
let value = '';
const entry = binding[key];
if (entry.datatype) {
// e.g., "\"6900000\"^^http://www.w3.org/2001/XMLSchema#integer"
const literal = `"${entry.value}"^^${entry.datatype}`;
value = JSON.stringify(literal);
} else if (entry['xml:lang']) {
// e.g., "\"text here\"@en"
const literal = `"${entry.value}"@${entry['xml:lang']}`;
value = JSON.stringify(literal);
} else if (entry.type === 'uri') {
// URIs should be escaped and quoted directly
value = JSON.stringify(entry.value);
} else {
// For plain literals, wrap in quotes and stringify
const literal = `"${entry.value}"`;
value = JSON.stringify(literal);
}
const isLast = index === keys.length - 1;
string += ` "${key}": ${value}${isLast ? '' : ','}\n`;
});
const isLastBinding = bindingIndex === bindings.length - 1;
string += ` }${isLastBinding ? '\n' : ',\n'}`;
output += string;
});
output += ']';
response = output;
}
} else {
response = result.data;
}
// Handle Blazegraph special characters corruption
if (this.hasUnicodeCodePoints(response)) {
response = this.decodeUnicodeCodePoints(response);
}
response = this.utfConverter(response);
return response;
}
async healthCheck(repository) {
try {
const response = await axios.get(
`${this.repositories[repository].url}/blazegraph/status`,
{},
);
if (response.data !== null) {
return true;
}
return false;
} catch (e) {
return false;
}
}
async queryVoid(repository, query, timeout) {
return axios.post(this.repositories[repository].sparqlEndpoint, query, {
headers: {
'Content-Type': 'application/sparql-update; charset=UTF-8',
'X-BIGDATA-MAX-QUERY-MILLIS': timeout,
},
});
}
async deleteRepository(repository) {
const { url, name } = this.repositories[repository];
this.logger.info(
`Deleting ${this.getName()} triple store repository: ${repository} with name: ${name}`,
);
if (await this.repositoryExists(repository)) {
await axios
.delete(`${url}/blazegraph/namespace/${name}`, {})
.catch((e) =>
this.logger.error(
`Error while deleting ${this.getName()} triple store repository: ${repository} with name: ${name}. Error: ${
e.message
}`,
),
);
}
}
async repositoryExists(repository) {
const { url, name } = this.repositories[repository];
try {
await axios.get(`${url}/blazegraph/namespace/${name}/properties`, {
params: {
'describe-each-named-graph': 'false',
},
headers: {
Accept: 'application/ld+json',
},
});
return true;
} catch (error) {
if (error.response && error.response.status === 404) {
// Expected error: GraphDB is up but has not created node0 repository
// Ot-node will create repo in initialization
return false;
}
this.logger.error(
`Error while getting ${this.getName()} repositories. Error: ${error.message}`,
);
return false;
}
}
getName() {
return 'OtBlazegraph';
}
}
export default OtBlazegraph;