Generic SPARQL query generator for Node.js
This package provides of the following features:
- Class Manager, ontology class cache to use OWL classes as a schema
- Prefix Manager, manages the prefixes for the named prefix notation of ontology IRIs
- Generator, SPARQL generator for class, property and annotation operations as well as CRUD-Operations for individuals
The concept is to:
- specify classes in a W3C-compliant ontology modelling tool
- import the model into a W3C-compliant semantic graph-database (e.g. Ontotext GraphDB)
- read the classes of the model into a Class Cache in a node.js application
- let the node.js application automatically generate the SPARQL queries for CRUD operations for the individuals of the specified classes (done by this library)
- Perform the generated SPARQL queries against the semantic graph-database to manage the individuals (supported by the enapso-graphdb-client package)
This package is SPARQL 1.1 compliant. It is designed to run on all SPARQL 1.1 compliant databases. The following demos use the Enapso GraphDB Client. Therefore, these examples require a running GraphDB 8.x/9.x instance on localhost at port 7200. The demos as well as the automated tests require a fully working Ontotext GraphDB repository "Test" and a user "Test" with the password "Test" being set up, which has read/write access to the "Test" Repository. To run the demo.js first import the foundation Ontology (as available in demo folder) in Test repository and to run the test cases import the EnapsoOntologyRepository (as available in test folder) in Test repository Get the latest version of GraphDB for free at https://www.ontotext.com/products/graphdb/.
This project is actively developed and maintained. To discuss questions and suggestions with the Enapso and GraphDB community, we'll be happy to meet you in our forum at https://www.innotrade.com/forum/.
npm i @innotrade/enapso-sparql-tools --save
There we do configuration to create an connection with GraphDB using enapso graphdb client which needed for building class cache and running the demo. In following code we have also different method are used for building classcache like for getting all classes we use getAllClasses method, getClassProperties for getting object and data properties of a class.
const { EnapsoGraphDBClient } = require('@innotrade/enapso-graphdb-client'),
{ EnapsoLogger } = require('@innotrade/enapso-logger');
global.enlogger = new EnapsoLogger();
const EnapsoSPARQLTools = '@innotrade/enapso-sparql-tools';
const GRAPHDB_BASE_URL = 'http://localhost:7200',
GRAPHDB_REPOSITORY = 'Test';
const NS_AUTH = 'http://ont.enapso.com/repo#',
PREFIX_AUTH = 'enrepo';
// the default prefixes for all SPARQL queries
const AUTH_PREFIXES = [
EnapsoGraphDBClient.PREFIX_OWL,
EnapsoGraphDBClient.PREFIX_RDF,
EnapsoGraphDBClient.PREFIX_RDFS,
EnapsoGraphDBClient.PREFIX_XSD,
{
prefix: PREFIX_AUTH,
iri: NS_AUTH
}
];
const AUTH = {
graphDBEndpoint: null,
authentication: null,
defaultBaseIRI: NS_AUTH,
defaultPrefix: PREFIX_AUTH,
defaultIRISeparator: '#',
query: async function (sparql) {
let query = await this.graphDBEndpoint.query(sparql);
let resp;
if (query.success) {
resp = await this.graphDBEndpoint.transformBindingsToResultSet(
query,
{
dropPrefixes: true
}
);
} else {
let lMsg = query.message;
if (400 === query.statusCode) {
lMsg += ', check your query for potential errors';
} else if (403 === query.statusCode) {
lMsg +=
', check if user "' +
GRAPHDB_USERNAME +
'" has appropriate access rights to the Repository ' +
'"' +
this.graphDBEndpoint.getRepository() +
'"';
}
resp = {
total: 0,
success: false,
message: lMsg
};
}
return resp;
},
update: async function (sparql, params) {
let resp = await this.graphDBEndpoint.update(sparql, params);
if (!resp.success) {
let lMsg = resp.message;
if (400 === resp.statusCode) {
lMsg += ', check your query for potential errors';
} else if (403 === resp.statusCode) {
lMsg +=
', check if user "' +
GRAPHDB_USERNAME +
'" has appropriate access rights to the Repository ' +
'"' +
this.graphDBEndpoint.getRepository() +
'"';
}
}
return resp;
},
// retrieve all classes from the graph
getAllClasses: async function () {
let generated = this.enSPARQL.getAllClasses();
// enlogger.log('SPARQL:\n' + generated.sparql);
return this.query(generated.sparql);
},
// retrieve all properties from a given class
getClassProperties: async function (cls) {
let generated = this.enSPARQL.getClassProperties(cls);
// enlogger.log('SPARQL:\n' + generated.sparql);
return this.query(generated.sparql);
},
// generates an in-memory class from a SPARQL result set
generateClassFromClassProperties: function (ns, name, classProps) {
let cls = new EnapsoSPARQLTools.Class(ns, name);
for (let propRec of classProps.records) {
// todo: here we need to add the restrictions, domain, range, min, max, exactly etc.
let prop = new EnapsoSPARQLTools.Property(
ns,
propRec.prop,
propRec.type,
propRec.range,
propRec.domain
);
// add the property to the classs
cls.addProperty(prop);
}
return cls;
},
// builds the class cache for all or selected classes
buildClassCache: async function () {
let classCache = new EnapsoSPARQLTools.ClassCache();
// get all classes of the database
let classes = await this.getAllClasses();
// iterate through all returned classes
for (let clsRec of classes.records) {
let className = clsRec.class;
// get the properties of the given class
res = await this.getClassProperties(className);
// generate an in-memory class of the retrieved properties
let cls = this.generateClassFromClassProperties(
NS_AUTH,
className,
res
);
// add the class to the cache
classCache.addClass(cls);
}
return classCache;
},
demo: async function () {
// instantiate a prefix manager
enlogger.setLevel(EnapsoLogger.ALL);
this.enPrefixManager = new EnapsoSPARQLTools.PrefixManager(
AUTH_PREFIXES
);
// in case no prefix is given for a certain resource identifier use the EDO: here
this.enPrefixManager.setDefaultPrefix(PREFIX_AUTH);
// create a SPARQL generator using the prefix manager
this.enSPARQL = new EnapsoSPARQLTools.Generator({
prefixManager: this.enPrefixManager
});
// instantiate a GraphDB connector and connect to GraphDB
this.graphDBEndpoint = new EnapsoGraphDBClient.Endpoint({
baseURL: GRAPHDB_BASE_URL,
repository: GRAPHDB_REPOSITORY,
prefixes: this.enPrefixManager.getPrefixesForConnector()
});
classCache = await this.buildClassCache();
}
};
(async () => {
await AUTH.demo();
})();
One essential component of the Enapso SPARQL Toolbox is its class cache. If you plan to run CRUD commands for individuals based on the property definitions for OWL classes, the SPARQL query requires to know about the fields of the class. In order to not need to load them with each and every query of the individuals, the Enapso SPARQL toolbox comes with an In-Memory-Class-Cache.
On start of an application all classes on an ontology are loaded into the cache. For the sake of performance, to read one or all individuals of a certain class, the SPARQL generator routines utilize the cache.
With the method createIndividualByClass(args)a new individual of a given class is created in the ontology. The following four parameters
cls
: class definition from the In-Memory Class-Cache as a reference for the individual to be created.
ind
: an object providing the values for the individual of the selected class (example see below).
baseiri(optional)
: an variable in which we define the baseiri which use along the name of individual if baseiri pass else it will check
If ind contains an field iri, this IRI will be used as explicit IRI for the new individual. If ind does not contain an iri field, the IRI will be auto generated as UUID V4 by the method.
options:
prefixSubjects
: true|false, whether to use prefixed names (default) or full IRI for subjects (here the individual to be updated).
prefixPredicates
: true|false, whether to use prefixed names (default) for predicates or full IRI for the predicates (here the properties).
prefixObjects
: true|false, whether to use prefixed names (default) for objects or full IRI for the related individuals (here the object properties).
createIndividualByClass: async function (args) {
let generated = this.enSPARQL.createIndividualByClass(args);
//enlogger.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql, { iri: generated.iri });
}
Initializing variables using that variable call the createIndividualByClass
method
let Tenant = classCache.getClassByIRI(NS_AUTH + 'Tenant');
// insert a new individual based on the in-memory class
res = await this.createIndividualByClass({
cls: Tenant,
baseiri: baseiri,
ind: {
name: 'Test Company'
}
});
// save the iri for this individual
iri = res.params.iri;
out = JSON.stringify(res, null, 2);
console.log('Creating individual by class:' + out);
The method updateIndividualByClass(cls, iri, ind) updates an individual in the ontology based on its class and iri. The method requires the following inbound parameters:
cls
: class definition from the In-Memory Class-Cache as a reference for the individual to be created.
iri
: the IRI of the individual to be updated.
ind
: an object providing the values for the individual of the selected class (example see below).
updateIndividualByClass: async function (cls, iri, ind) {
let generated = this.enSPARQL.updateIndividualByClass(cls, iri, ind);
//enlogger.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the updateIndividualByClass
method
let Tenant = classCache.getClassByIRI(NS_AUTH + 'Tenant');
let iri =
'http://ont.enapso.com/repo#Tenant_e7e124a2_3a7b_4333_8f51_5f70d48f0bfe';
// Update an exisitng individual based on the in-memory class
res = await this.updateIndividualByClass(Tenant, baseiri, {
name: 'Test'
});
out = JSON.stringify(res, null, 2);
console.log('Update an individual by class:' + out);
The method getIndividualsByClass(args) returns all instances of a certain class. In order to run this method, the class cache needs to be properly initialized, since the properties of the class are loaded from that cache. The following arguments are supported:
cls
:class definition from the In-Memory Class-Cache as a reference for the individual to be created.
join
:An object in which we pass nested objects to get the whole tree structure of the class using there relation and classes name there we have two types of relation childtoMasterRelation and mastertochildRelation it is optional
filter
: filter used to get a specific individual of a class
getIndividualsByClass: async function (args) {
let generated = this.enSPARQL.getIndividualsByClass(args);
enlogger.log("SPARQL:\n" + generated.sparql);
return this.query(generated.sparql);
}
Initializing variables using that variable call the getIndividualsByClass
method
this.Tenant = this.classCache.getClassByIRI(NS_AUTH + 'Tenant');
this.Environment = this.classCache.getClassByIRI(NS_AUTH + 'Environment');
this.Host = this.classCache.getClassByIRI(NS_AUTH + 'Host');
this.DatabaseInstance = this.classCache.getClassByIRI(
NS_AUTH + 'DatabaseInstance'
);
this.Repository = this.classCache.getClassByIRI(NS_AUTH + 'Repository');
this.Graph = this.classCache.getClassByIRI(NS_AUTH + 'Graph');
let joins = [
// first join (for tenants) on level 1
{
cls: this.Environment,
child2MasterRelation: 'hasTenant',
joins: [
{
cls: this.Host,
child2MasterRelation: 'hasEnvironment',
joins: [
{
cls: this.DatabaseInstance,
child2MasterRelation: 'hasHost',
joins: [
{
cls: this.Repository,
child2MasterRelation: 'hasDatabaseInstance',
joins: [
{
cls: this.Graph,
child2MasterRelation: 'hasRepository'
}
]
}
]
}
]
}
]
}
];
let filter = [
{
key: '$sparql',
value: 'regEx(?name, "Ebner", "i")'
}
];
// Read individual based on the in-memory class
res = await this.getIndividualsByClass({
cls: this.Tenant,
joins: joins,
filter: filter
});
out = JSON.stringify(res, null, 2);
console.log('Read individuals by class:' + out);
The deleteIndividual(args) deletes an arbitrary individual in the given addressed by its IRI and also its entire subtree structure but that option is optional.The following arguments are supported:
iri
:IRI of an individual of a class
join
:An object in which we pass nested objects to delete the whole tree structure of the class using there relation and classes name there we have two types of relation childtoMasterRelation and mastertochildRelation it is optional.
deleteIndividual: async function (args) {
let generated = this.enSPARQL.deleteResource(args);
enlogger.log("SPARQL:\n" + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the deleteIndividual
method
let iri = 'enrepo:Tenant_0143e7ee_fbdd_45b3_879f_fedc78e42ab4';
let joins = [
// first join (for tenants) on level 1
{
cls: 'Environment',
child2MasterRelation: 'hasTenant',
joins: [
{
cls: 'Host',
child2MasterRelation: 'hasEnvironment',
joins: [
{
cls: 'DatabaseInstance',
child2MasterRelation: 'hasHost',
joins: [
{
cls: 'Repository',
child2MasterRelation: 'hasDatabaseInstance',
joins: [
{
cls: 'Graph',
child2MasterRelation: 'hasRepository'
}
]
}
]
}
]
}
]
}
];
res = await this.deleteIndividual({
iri: iri,
joins: joins
});
out = JSON.stringify(res, null, 2);
console.log('Delete individuals by iri:' + out);
Create Relation method create the relation between two indivdual of different classes we pass following parameter.
master
: the iri of master class individual
Relation
: the object property which relation want to create
child
: the iri of child class individual
createRelation: async function (master, property, child) {
let generated = this.enSPARQL.createRelation(master, property, child);
//console.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the createRelation
method
let master =
'http://ont.enapso.com/repo#Tenant_e7e124a2_3a7b_4333_8f51_5f70d48f0bfe';
let relation = 'hasTenant';
let child =
'http://ont.enapso.com/repo#Environment_833a44cc_ec58_4202_b44d_27460ae94e2d';
res = await this.createRelation(master, relation, child);
out = JSON.stringify(res, null, 2);
console.log('Create Relation between two individuals:' + out);
Delete Relation method to delete the relation between two indivdual of different classes we pass following parameter.
master
: the iri of master class individual
Relation
: the object property which relation want to create
child
: the iri of child class individual
deleteRelation: async function (master, property, child) {
let generated = this.enSPARQL.deleteRelation(master, property, child);
//console.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the createRelation
method
let master =
'http://ont.enapso.com/repo#Tenant_e7e124a2_3a7b_4333_8f51_5f70d48f0bfe';
let relation = 'hasTenant';
let child =
'http://ont.enapso.com/repo#Environment_833a44cc_ec58_4202_b44d_27460ae94e2d';
res = await this.deleteRelation(master, relation, child);
out = JSON.stringify(res, null, 2);
console.log('Delete Relation between two individuals:' + out);
This method delete the given property of given class. We pass following parameter.
cls
: name of the class whoose property you wanna delete
property
: name of the property which you wanna delete.
deletePropertyOfClass(args) {
let generated = this.enSPARQL.deleteGivenPropertyOfClass(args);
//enlogger.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the deletePropertyOfClass
method
let cls = 'Environment';
let property = 'name';
res = await this.deletePropertyOfClass({ cls: cls, dataProperty: property });
out = JSON.stringify(res, null, 2);
console.log('Delete Property of the class :' + out);
This method to delete the labels of all individuals of given class of an specific language label. We pass following parameter.
cls
: name of the class whoose property you wanna delete
language
: name of language whoose label match with this language will be deleted.
deleteLabelOfEachClassIndividual(args) {
let generated = this.enSPARQL.deleteLabelOfEachClassIndividual(args);
//enlogger.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the deleteLabelOfEachClassIndividual
method
let cls = 'Environment';
let language = 'en';
res = await this.deleteLabelOfEachClassIndividual({
cls: cls,
labelLanguage: language
});
out = JSON.stringify(res, null, 2);
console.log('Delete label of each individual of a class :' + out);
This method to copy the label of specific language of each individual of a class and insert the label value as an given property of each individual of the given class We pass following parameter.
cls
: name of the class whoose label you want to copy and insert into given data property
language
: name of language whoose label match with this language will be copies.
property
: name of the property where you want to paste the label value.
copyLabelToDataPropertyOfEachIndividual(args) {
let generated = this.enSPARQL.copyLabelToDataPropertyOfEachIndividual(args);
//enlogger.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the copyLabelToDataPropertyOfEachIndividual
method
let cls = 'Environment';
let property = 'name';
let language = 'en';
res = await this.copyLabelToDataPropertyOfEachIndividual({
cls: cls,
labelLanguage: language,
dataProperty: property
});
out = JSON.stringify(res, null, 2);
console.log('Copy label of each individual of a class into Property :' + out);
This method to copy the property each individual of a class and insert the property value as an label of each individual of a class and assign a specific langauges.We pass following parameter.
cls
: name of the class whose property value you want to copy and insert into given label
language
: name of language which you want to assign while creating label.
property
: name of the property where you want to copy the property value.
copyDataPropertyToLabelOfEachIndividual(args) {
let generated = this.enSPARQL.copyDataPropertyToLabelOfEachIndividual(args);
//enlogger.log('SPARQL:\n' + generated.sparql);
return this.update(generated.sparql);
}
Initializing variables using that variable call the copyDataPropertyToLabelOfEachIndividual
method
let cls = 'Environment';
let property = 'name';
let language = 'en';
res = await this.copyDataPropertyToLabelOfEachIndividual({
cls: cls,
labelLanguage: language,
dataProperty: property
});
out = JSON.stringify(res, null, 2);
console.log('Delete label of each individual of a class :' + out);