/**********************************************************
*
* COEUS
* Semantic Web Application Framework
*
* Javascript API
*
*
* Example interactions
var sparqler = new SPARQL.Service("http://bioinformatics.ua.pt/coeus/sparql");
sparqler.setPrefix("dc", "http://purl.org/dc/elements/1.1/");
sparqler.setPrefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
sparqler.setPrefix("coeus", "http://bioinformatics.ua.pt/coeus/resource/");
sparqler.setRequestHeader("Authentication", "Basic: " + basicAuthString);
//sparqler.wantOutputAs("application/json"); // for now we only do JSON
var query = sparqler.createQuery();:
// passes standard JSON results object to success callback
query.query("SELECT ?who ?mbox WHERE { ldf:LDF foaf:knows ?who . ?who foaf:mbox ?mbox }",
{failure: onFailure, success: function(json) { for (var x in json.head.vars) { ... } ...}}
);
// passes boolean value to success callback
query.ask("ASK ?person WHERE { ?person foaf:knows [ foaf:name "Dan Connolly" ] }",
{failure: onFailure, success: function(bool) { if (bool) ... }}
);
// passes a single vector (array) of values representing a single column of results to success callback
var addresses = query.selectValues("SELECT ?mbox WHERE { _:someone foaf:mbox ?mbox }",
{failure: onFailure, success: function(values) { for (var i = 0; i < values.length; i++) { ... values[i] ...} } }
);
// passes a single value representing a single row of a single column (variable) to success callback
var myAddress = query.selectSingleValue("SELECT ?mbox WHERE {ldf:LDF foaf:mbox ?mbox }",
{failure: onFailure, success: function(value) { alert("value is: " + value); } }
);
// shortcuts for all of the above (w/o ability to set any query-specific graphs or prefixes)
sparqler.query(...) sparqler.ask(...) sparqler.selectValues(...) sparqler.selectSingleValue(...)
*/
var SPARQL = {}; // SPARQL namespace
/**
* Both SPARQL service objects and SPARQL query objects receive one query utility method
* per entry in this dictionary. The key is the name of the method, and the value is a function
* that transforms the standard JSON output into a more useful form. The return value of a
* transformation function is passed into any 'success' callback function provided when the query
* is issued. The following transformations are included:
* + query -- identity transform; returns the JSON structure unchanged
* + ask -- for ASK queries; returns a boolean value indicating the answer to the query
* + selectValues -- for SELECT queries with a single variable; returns an array containing
* the answers to the query
* + selectSingleValue -- for SELECT queries returning one column with one row; returns the
* value in the first (and presumably, only) cell in the resultset
* + selectValueArrays -- for SELECT queries returning independent columns; returns a hash
* keyed on variable name with values as arrays of answers for that variable. Useful
* for UNION queries.
* Note that all of the transformations that return values directly lose any type information
* and the ability to distinguish between URIs, blank nodes, and literals.
*/
SPARQL._query_transformations = {
query: function (o) {
return o;
},
ask: function (o) {
return o["boolean"];
},
selectValues: function (o) {
var v = o.head.vars[0]; // assume one variable
var values = [];
for (var i = 0; i < o.results.bindings.length; i++)
values.push(o.results.bindings[i][v].value);
return values;
},
selectSingleValue: function(o) {
return o.results.bindings[0][o.head.vars[0]].value;
},
selectValueArrays: function(o) {
// factor by value (useful for UNION queries)
var ret = {};
for (var i = 0; i < o.head.vars.length; i++)
ret[o.head.vars[i]] = [];
for (var i = 0; i < o.results.bindings.length; i++)
for (var v in o.results.bindings[i])
if (ret[v] instanceof Array) ret[v].push(o.results.bindings[i][v].value);
return ret;
},
selectValueHashes: function(o) {
var hashes = [];
for (var i = 0; i < o.results.bindings.length; i++) {
var hash = {};
for (var v in o.results.bindings[i])
hash[v] = o.results.bindings[i][v].value;
hashes.push(hash);
}
return hashes;
}
};
SPARQL.statistics = {
queries_sent : 0,
successes : 0,
failures : 0
};
// A SPARQL service represents a single endpoint which implements the HTTP (GET or POST)
// bindings of the SPARQL Protocol. It provides convenience methods to set dataset and
// prefix options for all queries created for this endpoint.
SPARQL.Service = function(endpoint) {
//---------------
// private fields
var _endpoint = endpoint;
var _default_graphs = [];
var _named_graphs = [];
var _prefix_map = {};
var _method = 'POST';
var _output = 'json';
var _max_simultaneous = 0;
var _request_headers = {};
//----------
// accessors
this.endpoint = function() {
return _endpoint;
};
this.defaultGraphs = function() {
return _default_graphs;
};
this.namedGraphs = function() {
return _named_graphs;
};
this.prefixes = function() {
return _prefix_map;
};
this.method = function() {
return _method;
};
this.output = function() {
return _output;
};
this.maxSimultaneousQueries = function() {
return _max_simultaneous;
};
this.requestHeaders = function() {
return _request_headers;
};
//---------
// mutators
function _add_graphs(toAdd, arr) {
if (toAdd instanceof Array)
for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]);
else
arr.push(toAdd);
}
this.addDefaultGraph = function(g) {
_add_graphs(g, this.defaultGraphs());
};
this.addNamedGraph = function(g) {
_add_graphs(g, this.namedGraphs());
};
this.setPrefix = function(p, u) {
this.prefixes()[p] = u;
};
this.createQuery = function(p) {
return new SPARQL.Query(this, p);
};
this.setMethod = function(m) {
if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported.");
_method = m;
};
this.setOutput = function(o) {
_output = o;
};
this.setMaxSimultaneousQueries = function(m) {
_max_simultaneous = m;
};
this.setRequestHeader = function(h, v) {
_request_headers[h] = v;
};
//---------------
// protected methods (should only be called within this module
this._active_queries = 0;
this._queued_queries = [];
this._next_in_queue = 0;
this._canRun = function() {
return this.maxSimultaneousQueries() <= 0 || this._active_queries < this.maxSimultaneousQueries();
};
this._queue = function(q,f, p) {
if (!p) p = 0;
if (p > 0) {
for (var i = 0; i < this._queued_queries.length; i++) {
if (this._queued_queries[i] != null && this._queued_queries[i][2] < p) {
this._queued_queries.splice(i, 0, [q, f, p]);
return;
}
}
}
this._queued_queries.push([q,f,p]);
};
this._markRunning = function(q) {
this._active_queries++;
};
this._markDone = function(q) {
this._active_queries--;
//document.getElementById('log').innerHTML+="query done. " + this._active_queries + " queries still active.
";
if (this._queued_queries[this._next_in_queue] != null && this._canRun()) {
var a = this._queued_queries[this._next_in_queue];
this._queued_queries[this._next_in_queue++] = null;
// a[0] is query object, a[1] is function to run query
//document.getElementById('log').innerHTML += "running query from Q
";
a[1]();
}
};
//---------------
// public methods
// use our varied transformations to create the various shortcut methods of actually
// issuing queries without explicitly creating a query object
for (var query_form in SPARQL._query_transformations) {
// need the extra function to properly scope query_form (qf)
this[query_form] = (function(qf) {
return function(queryString, callback) {
var q = this.createQuery();
q._doQuery(queryString, callback, SPARQL._query_transformations[qf]);
};
})(query_form);
}
//------------
// constructor
if (!_endpoint)
return null;
return this;
}
/**
* A SPARQL query object should be created using the createQuery method of a SPARQL
* service object. It allows prefixes and datasets to be defined specifically for
* a single query, and provides introspective methods to see the query string and the
* full (HTTP GET) URL of the query.
*/
SPARQL.Query = function(service, priority) {
//---------------
// private fields
var _conn = null;
var _service = service;
var _default_graphs = clone_obj(service.defaultGraphs()); // prevent future updates from affecting us
var _named_graphs = clone_obj(service.namedGraphs());
var _prefix_map = clone_obj(service.prefixes());
var _user_query = ''; // doesn't include auto-generated prefix declarations
var _method = service.method();
var _output = service.output();
var _priority = priority || 0;
var _request_headers = clone_obj(service.requestHeaders());
//------------------
// private functions
function _create_json(text) {
if (!text)
return null;
// make sure this is safe JSON
// see: http://www.ietf.org/internet-drafts/draft-crockford-jsonorg-json-03.txt
// (1) strip out quoted strings
var no_strings = text.replace(/"(\\.|[^"\\])*"/g, '');
// (2) make sure that all the characters are explicitly part of the JSON grammar
// (in particular, note as discussed in the IETF submission, there are no assignments
// or function invocations allowed by this reg. exp.)
var hasBadCharacter = /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(no_strings);
// (3) evaluate the JSON string, returning its contents
if (!hasBadCharacter) {
try {
return eval('(' + text + ')');
} catch (e) {
return null;
}
}
return null;
}
function clone_obj(o) {
var o2 = o instanceof Array ? [] : {};
for(var x in o) {
o2[x] = o[x];
}
return o2;
}
//----------------
// private methods
this._doCallback = function(cb, which, arg) {
//document.getElementById('log').innerHTML += "_doCallback ...
";
var user_data = "argument" in cb ? cb.argument : null;
if (which in cb) {
if (cb.scope) {
cb[which].apply(cb.scope, [arg, user_data]);
} else {
cb[which](arg, user_data);
}
}
}
this._queryFailure = function(xhr, arg) {
SPARQL.statistics.failures++;
_service._markDone(this);
this._doCallback(arg.callback, 'failure', xhr /* just pass through the connection response object */);
};
this._querySuccess = function(xhr, arg) {
//alert(xhr.responseText);
SPARQL.statistics.successes++;
_service._markDone(this);
this._doCallback(arg.callback, 'success', arg.transformer(
_output == 'json' ? _create_json(xhr.responseText) : xhr.responseText
));
};
function getXmlHttpRequest(url) {
// right now, this only does Firefox (Opera? Safari?)
return new XMLHttpRequest();
}
this._doQuery = function(queryString, callback, transformer) {
_user_query = queryString;
if (_service._canRun()) {
try {
if (_method != 'POST' && _method != 'GET')
throw("HTTP methods other than GET and POST are not supported.");
var url = _method == 'GET' ? this.queryUrl() : this.service().endpoint();
var xhr = getXmlHttpRequest(url);
var content = null;
try {
if (!document.domain || (url.match(/^https?:\/\//) && url.slice(7, document.domain.length + 7) != document.domain && window.netscape && netscape.security && netscape.security.PrivilegeManager)) {
netscape.security.PrivilegeManager.enablePrivilege( "UniversalBrowserRead");
netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect");
}
} catch(e) {
alert("Cross-site requests prohibited. You will only be able to SPARQL the origin site: " + e);
return;
}
xhr.open(_method, url, true /* async */);
// set the headers, including the content-type for POSTed queries
for (var header in this.requestHeaders())
if (typeof(this.requestHeaders()[header]) != "function")
xhr.setRequestHeader(header, this.requestHeaders()[header]);
if (_method == 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
content = this.queryParameters();
}
SPARQL.statistics.queries_sent++;
_service._markRunning(this);
var callbackData = {
scope:this,
success: this._querySuccess,
failure: this._queryFailure,
argument: {
transformer: transformer,
callback: callback
}
};
// I've seen some strange race-condition behavior (strange since normally
// JS is single-threaded, so synchronization conditions don't occur barring
// reentrancy) with onreadystatechange. Instead, we poll asynchronously to
// determine when the request is done.
var token = window.setInterval(
function () {
if (xhr.readyState == 4) { // ready!
// clear this handler
window.clearInterval(token);
// we check the status to know which callback to call
if (xhr.status >= 200 && xhr.status < 300)
callbackData.success.apply(callbackData.scope, [xhr, callbackData.argument]);
else
callbackData.failure.apply(callbackData.scope, [xhr, callbackData.argument]);
}
},
200 /* maybe this should be customizable */
);
xhr.send(content);
} catch (e) {
alert("Error sending SPARQL query: " + e);
}
} else {
var self = this;
_service._queue(self, function() {
self._doQuery(queryString, callback, transformer);
}, _priority);
}
};
//----------
// accessors
this.request = function() {
return _conn;
};
this.service = function() {
return _service;
};
this.defaultGraphs = function() {
return _default_graphs;
};
this.namedGraphs = function() {
return _named_graphs;
};
this.prefixes = function() {
return _prefix_map;
};
this.method = function() {
return _method;
};
this.requestHeaders = function() {
return _request_headers;
};
/**
* Returns the SPARQL query represented by this object. The parameter, which can
* be omitted, determines whether or not auto-generated PREFIX clauses are included
* in the returned query string.
*/
this.queryString = function(excludePrefixes) {
var preamble = '';
if (!excludePrefixes) {
for (var prefix in this.prefixes()) {
if(typeof(this.prefixes()[prefix]) != 'string') continue;
preamble += 'PREFIX ' + prefix + ': <' + this.prefixes()[prefix] + '> ';
}
}
return preamble + _user_query;
};
/**
* Returns the HTTP query parameters to invoke this query. This includes entries for
* all of the default graphs, the named graphs, the SPARQL query itself, and an
* output parameter to specify JSON (or other) output is desired.
*/
this.queryParameters = function () {
var urlQueryString = '';
var i;
// add default and named graphs to the protocol invocation
for (i = 0; i < this.defaultGraphs().length; i++) urlQueryString += 'default-graph-uri=' + encodeURIComponent(this.defaultGraphs()[i]) + '&';
for (i = 0; i < this.namedGraphs().length; i++) urlQueryString += 'named-graph-uri=' + encodeURIComponent(this.namedGraphs()[i]) + '&';
// specify JSON output (currently output= supported by latest Joseki) (or other output)
urlQueryString += 'output=' + _output + '&';
return urlQueryString + 'query=' + encodeURIComponent(this.queryString());
}
/**
* Returns the HTTP GET URL to invoke this query. (Note that this returns a full HTTP GET URL
* even if this query is set to actually use POST.)
*/
this.queryUrl = function() {
var url = this.service().endpoint() + '?';
return url + this.queryParameters();
};
//---------
// mutators
function _add_graphs(toAdd, arr) {
if (toAdd instanceof Array)
for (var i = 0; i < toAdd.length; i++) arr.push(toAdd[i]);
else
arr.push(toAdd);
}
this.addDefaultGraph = function(g) {
_add_graphs(g, this.defaultGraphs());
};
this.addNamedGraph = function(g) {
_add_graphs(g, this.namedGraphs());
};
this.setPrefix = function(p, u) {
this.prefixes()[p] = u;
};
this.setMethod = function(m) {
if (m != 'GET' && m != 'POST') throw("HTTP methods other than GET and POST are not supported.");
_method = m;
};
this.setRequestHeader = function(h, v) {
_request_headers[h] = v;
};
//---------------
// public methods
// use our varied transformations to create the various methods of actually issuing
// queries
for (var query_form in SPARQL._query_transformations) {
// need the extra function to properly scope query_form (qf)
this[query_form] = (function(qf) {
return function(queryString, callback) {
this._doQuery(queryString, callback, SPARQL._query_transformations[qf]);
};
})(query_form);
}
//------------
// constructor
return this;
};
/**********************************************************
Original Copyright (c) 2006, 2007
Lee Feigenbaum ( lee AT thefigtrees DOT net )
Elias Torres ( elias AT torrez DOT us )
Wing Yung ( wingerz AT gmail DOT com )
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
**********************************************************/