Skip to content

Commit

Permalink
Refactor codebase to use RDFJS terms instead of strings.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dexagod authored and rubensworks committed Apr 6, 2020
1 parent b38026f commit b014f67
Show file tree
Hide file tree
Showing 51 changed files with 1,367 additions and 1,062 deletions.
2 changes: 1 addition & 1 deletion packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ This package exposes the the following context entries:
* `View`: An abstract view. _Should be used as `extends` value when creating new views._
* `viewExtensions`: A view extension. _Should be used as key in a view._
* `viewCache`: If views should be cached. _Should be used as key in an HTML view._
* `n3Util`: The N3Util class. _Should be used as key in an HTML view._
* `viewHeader`: The view header title. _Should be used as key in an HTML view._

**Other:**
* `Server`: An HTTP server that provides access to Linked Data Fragments. This is enabled by default in `@ldf/server`. _Should be used as `@type` value._
* `dataFactory`: A factory object used to construct rdfjs terms._
* `title`: The server name. _Should be used as key in a `Server` config._
* `baseURL`: The base URL path for the server. _Should be used as key in a `Server` config._
* `port`: The port the server will bind with. _Should be used as key in a `Server` config._
Expand Down
7 changes: 6 additions & 1 deletion packages/core/components/Datasource.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@
"range": "xsd:boolean",
"unique": true,
"default": true
}
},
{
"@id": "ldfc:Datasource#dataFactory",
"comment": "Used to create rdfjs terms",
"unique": true
}
],
"constructorArguments": {
"@id": "ldfc:Datasource#constructorArgumentsObject",
Expand Down
9 changes: 0 additions & 9 deletions packages/core/components/View/Html.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@
"range": "xsd:boolean",
"unique": true
},
{
"@id": "ldfc:View/Html#n3Util",
"comment": "The N3Util class",
"unique": true
},
{
"@id": "ldfc:View/Html#urlData",
"inheritValues": {
Expand Down Expand Up @@ -54,10 +49,6 @@
"keyRaw": "cache",
"value": "ldfc:View/Html#cache"
},
{
"keyRaw": "N3Util",
"value": "ldfc:View/Html#n3Util"
},
{
"@id": "ldfc:Server#urlDataField"
},
Expand Down
1 change: 0 additions & 1 deletion packages/core/components/context.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"viewExtension": "ldfc:View#extension",
"viewExtensions": "ldfc:View#extension",
"viewCache": "ldfc:View/Html#cache",
"n3Util": "ldfc:View/Html#n3Util",
"viewTitle": "ldfc:View/Html#title",
"viewHeader": "ldfc:View/Html#header",

Expand Down
44 changes: 23 additions & 21 deletions packages/core/lib/datasources/Datasource.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ var fs = require('fs'),
_ = require('lodash'),
UrlData = require('../UrlData'),
BufferedIterator = require('asynciterator').BufferedIterator,
EventEmitter = require('events');
EventEmitter = require('events'),
stringToTerm = require('rdf-string').stringToTerm,
N3 = require('n3');

// Creates a new Datasource
class Datasource extends EventEmitter {
Expand All @@ -32,8 +34,9 @@ class Datasource extends EventEmitter {
this._request = options.request || require('request');
this._blankNodePrefix = options.blankNodePrefix || this._blankNodePrefix;
this._blankNodePrefixLength = this._blankNodePrefix.length;
this.dataFactory = options.dataFactory || N3.DataFactory;
if (options.graph) {
this._graph = options.graph;
this._graph = this.dataFactory.namedNode(options.graph);
this._queryGraphReplacements = Object.create(null);
this._queryGraphReplacements[''] = 'urn:ldf:emptyGraph';
this._queryGraphReplacements[options.graph] = '';
Expand Down Expand Up @@ -114,32 +117,31 @@ class Datasource extends EventEmitter {

// Translate blank nodes IRIs in the query to blank nodes
var blankNodePrefix = this._blankNodePrefix, blankNodePrefixLength = this._blankNodePrefixLength;
if (query.subject && query.subject.indexOf(blankNodePrefix) === 0)
query.subject = '_:' + query.subject.substr(blankNodePrefixLength);
if (query.object && query.object.indexOf(blankNodePrefix) === 0)
query.object = '_:' + query.object.substr(blankNodePrefixLength);
if (query.graph && query.graph.indexOf(blankNodePrefix) === 0)
query.graph = '_:' + query.graph.substr(blankNodePrefixLength);
if (query.subject && query.subject.value.indexOf(blankNodePrefix) === 0)
query.subject = this.dataFactory.blankNode(query.subject.value.substr(blankNodePrefixLength));
if (query.object && query.object.value.indexOf(blankNodePrefix) === 0)
query.object = this.dataFactory.blankNode(query.object.value.substr(blankNodePrefixLength));
if (query.graph && query.graph.value.indexOf(blankNodePrefix) === 0)
query.graph = this.dataFactory.blankNode(query.graph.value.substr(blankNodePrefixLength));

// If a custom default graph was set, query it as the default graph
if (this._graph && (query.graph in this._queryGraphReplacements))
query.graph = this._queryGraphReplacements[query.graph];
if (this._graph && query.graph && query.graph.value in this._queryGraphReplacements)
query.graph = stringToTerm(this._queryGraphReplacements[query.graph.value], this.dataFactory);

// Transform the received quads
var destination = new BufferedIterator(), outputQuads, graph = this._graph, self = this;
outputQuads = destination.map(function (quad) {
// Translate blank nodes in the result to blank node IRIs
if (quad.subject[0] === '_' && !self._skolemizeBlacklist[quad.subject])
quad.subject = blankNodePrefix + quad.subject.substr(2);
if (quad.object[0] === '_' && !self._skolemizeBlacklist[quad.object])
quad.object = blankNodePrefix + quad.object.substr(2);
if (quad.graph) {
if (quad.graph[0] === '_' && !self._skolemizeBlacklist[quad.graph])
quad.graph = blankNodePrefix + quad.graph.substr(2);
// Translate blank nodes in the result to blank node IRIs.
if (quad.subject && quad.subject.termType === 'BlankNode' && !self._skolemizeBlacklist[quad.subject.value])
quad.subject = self.dataFactory.namedNode(blankNodePrefix + quad.subject.value);
if (quad.object && quad.object.termType === 'BlankNode' && !self._skolemizeBlacklist[quad.object.value])
quad.object = self.dataFactory.namedNode(blankNodePrefix + quad.object.value);
if (quad.graph && quad.graph.termType !== 'DefaultGraph') {
if (quad.graph.termType === 'BlankNode' && !self._skolemizeBlacklist[quad.graph.value])
quad.graph = self.dataFactory.namedNode(blankNodePrefix + quad.graph.value);
}
// If a custom default graph was set, move default graph triples there
else if (graph)
quad.graph = graph;
// If a custom default graph was set, move default graph triples there.
quad.graph = quad.graph && quad.graph.termType !== 'DefaultGraph' ? quad.graph : (graph || quad.graph);
return quad;
});
outputQuads.copyProperties(destination, ['metadata']);
Expand Down
15 changes: 6 additions & 9 deletions packages/core/lib/datasources/IndexDatasource.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,16 @@ class IndexDatasource extends MemoryDatasource {

// Creates quads for each data source
_getAllQuads(addQuad, done) {
const quad = this.dataFactory.quad, namedNode = this.dataFactory.namedNode, literal = this.dataFactory.literal;
for (var name in this._datasources) {
var datasource = this._datasources[name], datasourceUrl = datasource.url;
if (!datasource.hide) {
triple(datasourceUrl, rdf + 'type', voID + 'Dataset');
triple(datasourceUrl, rdfs + 'label', datasource.title, true);
triple(datasourceUrl, dc + 'title', datasource.title, true);
triple(datasourceUrl, dc + 'description', datasource.description, true);
if (!datasource.hide && datasourceUrl) {
addQuad(quad(namedNode(datasourceUrl), namedNode(rdf + 'type'), namedNode(voID + 'Dataset')));
datasource.title && addQuad(quad(namedNode(datasourceUrl), namedNode(rdfs + 'label'), literal(datasource.title)));
datasource.title && addQuad(quad(namedNode(datasourceUrl), namedNode(dc + 'title'), literal(datasource.title)));
datasource.description && addQuad(quad(namedNode(datasourceUrl), namedNode(dc + 'description'), literal(datasource.description)));
}
}
function triple(subject, predicate, object, isLiteral) {
if (subject && predicate && object)
addQuad(subject, predicate, isLiteral ? '"' + object + '"' : object);
}
delete this._datasources;
done();
}
Expand Down
5 changes: 2 additions & 3 deletions packages/core/lib/datasources/MemoryDatasource.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MemoryDatasource extends Datasource {
// Prepares the datasource for querying
_initialize(done) {
var quadStore = this._quadStore = new N3Store();
this._getAllQuads(function (s, p, o, g) { quadStore.addTriple(s, p, o, g); }, done);
this._getAllQuads(function (quad) { quadStore.addQuad(quad); }, done);
}

// Retrieves all quads in the datasource
Expand All @@ -25,8 +25,7 @@ class MemoryDatasource extends Datasource {
// Writes the results of the query to the given quad stream
_executeQuery(query, destination) {
var offset = query.offset || 0, limit = query.limit || Infinity,
quads = this._quadStore.findByIRI(query.subject, query.predicate, query.object,
query.graph);
quads = this._quadStore.getQuads(query.subject, query.predicate, query.object, query.graph);
// Send the metadata
destination.setProperty('metadata', { totalCount: quads.length, hasExactCount: true });
// Send the requested subset of quads
Expand Down
4 changes: 2 additions & 2 deletions packages/core/lib/views/HtmlView.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var View = require('./View'),
q = require('q'),
path = require('path'),
_ = require('lodash'),
N3Util = require('n3').Util,
RdfString = require('rdf-string'),
UrlData = require('../UrlData');

// Creates a new HTML view with the given name and settings
Expand All @@ -15,7 +15,7 @@ class HtmlView extends View {
settings = settings || {};
settings.urlData = settings.urlData || new UrlData();
var defaults = {
cache: true, N3Util: N3Util,
cache: true, RdfString: RdfString,
assetsPath: settings.urlData.assetsPath || '/', baseURL: settings.urlData.baseURL || '/',
title: '', header: settings && settings.title,
};
Expand Down
79 changes: 32 additions & 47 deletions packages/core/lib/views/RdfView.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

var View = require('./View'),
N3 = require('n3'),
jsonld = require('jsonld'),
JsonLdSerializer = require('jsonld-streaming-serializer').JsonLdSerializer,
_ = require('lodash');

var dcTerms = 'http://purl.org/dc/terms/',
Expand All @@ -21,6 +21,7 @@ var contentTypes = 'application/trig;q=0.9,application/n-quads;q=0.7,' +
class RdfView extends View {
constructor(viewName, settings) {
super(viewName, contentTypes, settings);
this.dataFactory = N3.DataFactory;
}

// Renders the view with the given settings to the response
Expand Down Expand Up @@ -58,34 +59,36 @@ class RdfView extends View {
var datasources = settings.datasources;
for (var datasourceName in datasources) {
var datasource = datasources[datasourceName];
metadata(datasource.url, rdf + 'type', voID + 'Dataset');
metadata(datasource.url, rdf + 'type', hydra + 'Collection');
metadata(datasource.url, dcTerms + 'title', '"' + datasource.title + '"');
if (datasource.url) {
const quad = this.dataFactory.quad, namedNode = this.dataFactory.namedNode, literal = this.dataFactory.literal;
metadata(quad(namedNode(datasource.url), namedNode(rdf + 'type'), namedNode(voID + 'Dataset')));
metadata(quad(namedNode(datasource.url), namedNode(rdf + 'type'), namedNode(hydra + 'Collection')));
metadata(quad(namedNode(datasource.url), namedNode(dcTerms + 'title'), literal('"' + datasource.title + '"', 'en')));
}
}
}

// Creates a writer for Turtle/N-Triples/TriG/N-Quads
_createN3Writer(settings, response, done) {
var writer = new N3.Writer({ format: settings.contentType, prefixes: settings.prefixes }),
supportsGraphs = /trig|quad/.test(settings.contentType), metadataGraph;

const dataFactory = this.dataFactory;
return {
// Adds the data quad to the output
// NOTE: The first parameter can also be a quad object
data: function (s, p, o, g) {
// If graphs are unsupported, only write triples in the default graph
if (supportsGraphs || (p ? !g : !s.graph))
writer.addTriple(s, p, o, g);
data: function (quad) {
writer.addQuad(quad);
},
// Adds the metadata triple to the output
meta: function (s, p, o) {
// Relate the metadata graph to the data
meta: function (quad) {
// Relate the metadata graph to the data.
if (supportsGraphs && !metadataGraph) {
metadataGraph = settings.metadataGraph;
writer.addTriple(metadataGraph, primaryTopic, settings.fragmentUrl, metadataGraph);
writer.addQuad(dataFactory.namedNode(metadataGraph), dataFactory.namedNode(primaryTopic), dataFactory.namedNode(settings.fragmentUrl), dataFactory.namedNode(metadataGraph));
}
// Write the metadata triple
if (s && p && o && !N3.Util.isLiteral(s))
writer.addTriple(s, p, o, metadataGraph);
quad.graph = quad.graph.termType === 'DefaultGraph' ? (metadataGraph ? dataFactory.namedNode(metadataGraph) : dataFactory.defaultGraph()) : quad.graph;
writer.addQuad(quad);
},
// Ends the output and flushes the stream
end: function () {
Expand All @@ -99,50 +102,32 @@ class RdfView extends View {

// Creates a writer for JSON-LD
_createJsonLdWriter(settings, response, done) {
// Initialize triples, prefixes, and document base
var quads = { '@default': [] }, metadata = quads[settings.metadataGraph] = [],
prefixes = settings.prefixes || {}, context = _.omit(prefixes, ''), base = prefixes[''];
var prefixes = settings.prefixes || {}, context = _.omit(prefixes, ''), base = prefixes[''];
base && (context['@base'] = base);
const mySerializer = new JsonLdSerializer({ space: ' ', context: context, baseIRI: prefixes[''], useNativeTypes: true })
.on('error', done);
mySerializer.pipe(response);
mySerializer.on('error', (e => done(e)));
mySerializer.on('end', (e => done(null)));

const dataFactory = this.dataFactory;
return {
// Adds the data triple to the output
data: function (s, p, o, g) {
if (!p) g = s.graph, o = s.object, p = s.predicate, s = s.subject;
if (!g) g = '@default';
var graph = quads[g] || (quads[g] = []);
graph.push(toJsonLdTriple(s, p, o));
data: function (quad) {
mySerializer.write(quad);
},
// Adds the metadata triple to the output
meta: function (s, p, o) {
if (s && p && o && !N3.Util.isLiteral(s))
metadata.push(toJsonLdTriple(s, p, o));
meta: function (quad) {
quad.graph = quad.graph.termType === 'DefaultGraph' ? (settings.metadataGraph ? dataFactory.namedNode(settings.metadataGraph) : dataFactory.defaultGraph()) : quad.graph;
mySerializer.write(quad);
},
// Ends the output and flushes the stream
end: function () {
jsonld.fromRDF(quads, { format: false, useNativeTypes: true },
function (error, json) {
jsonld.compact(error ? {} : json, context, function (error, compacted) {
response.write(JSON.stringify(compacted, null, ' ') + '\n');
done(error);
});
});
// We need to wait for the serializer stream to end before calling done()
mySerializer.end();
},
};
}
}

// Converts a triple to the JSON-LD library representation
function toJsonLdTriple(subject, predicate, object) {
return {
subject: { value: subject, type: subject[0] !== '_' ? 'IRI' : 'blank node' },
predicate: { value: predicate, type: predicate[0] !== '_' ? 'IRI' : 'blank node' },
object: !N3.Util.isLiteral(object) ?
{ value: object, type: object[0] !== '_' ? 'IRI' : 'blank node' } :
{
value: N3.Util.getLiteralValue(object),
datatype: N3.Util.getLiteralType(object),
language: N3.Util.getLiteralLanguage(object),
},
};
}

module.exports = RdfView;
28 changes: 14 additions & 14 deletions packages/core/lib/views/ViewCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var _ = require('lodash'),
negotiate = require('negotiate'),
Util = require('../Util');

var ViewCollectionError = Util.createErrorType('ViewCollectionError');

// Creates a new ViewCollection
class ViewCollection {
Expand Down Expand Up @@ -41,21 +42,20 @@ class ViewCollection {
getViews(name) {
return this._views[name] || [];
}
}

var ViewCollectionError = ViewCollection.ViewCollectionError = Util.createErrorType('ViewCollectionError');

// Gets the best match for views with the given name that accommodate the request
ViewCollection.prototype.matchView = function (name, request) {
// Retrieve the views with the given name
var viewList = this._viewMatchers[name];
if (!viewList || !viewList.length)
throw new ViewCollectionError('No view named ' + name + ' found.');
// Negotiate the view best matching the request's requirements
var viewDetails = negotiate.choose(viewList, request)[0];
if (!viewDetails)
throw new ViewCollectionError('No matching view named ' + name + ' found.');
return viewDetails;
};
matchView(name, request) {
// Retrieve the views with the given name
var viewList = this._viewMatchers[name];
if (!viewList || !viewList.length)
throw new ViewCollectionError('No view named ' + name + ' found.');
// Negotiate the view best matching the request's requirements
var viewDetails = negotiate.choose(viewList, request)[0];
if (!viewDetails)
throw new ViewCollectionError('No matching view named ' + name + ' found.');
return viewDetails;
}
}
ViewCollection.ViewCollectionError = ViewCollectionError;

module.exports = ViewCollection;
12 changes: 7 additions & 5 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,18 @@
"lint": "eslint bin/* lib test"
},
"dependencies": {
"asynciterator": "^1.1.0",
"forwarded-parse": "^2.0.0",
"lodash": "^2.4.2",
"asynciterator": "^2.0.1",
"componentsjs": "3.3.0",
"forwarded-parse": "^2.1.0",
"jsonld-streaming-serializer": "^1.0.1",
"lodash": "^2.4.2",
"mime": "^1.3.4",
"n3": "^0.9.0",
"n3": "^1.3.5",
"negotiate": "^1.0.1",
"q": "^1.4.1",
"qejs": "^3.0.5",
"request": "^2.88.0"
"request": "^2.88.2",
"uritemplate": "^0.3.4"
},
"optionalDependencies": {
"access-log": "^0.3.9"
Expand Down
Loading

0 comments on commit b014f67

Please sign in to comment.