diff examples/browser/web/js/sparql.js @ 640:901803e1305f

First instance of audioDB browser code.
author mas01mj
date Thu, 08 Oct 2009 11:19:11 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/browser/web/js/sparql.js	Thu Oct 08 11:19:11 2009 +0000
@@ -0,0 +1,490 @@
+/**********************************************************
+  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.
+**********************************************************/
+
+/**
+ * Example client interactions
+ *
+ 
+ 	var sparqler = new SPARQL.Service("http://sparql.org/sparql");
+	sparqler.addDefaultGraph("http://thefigtrees.net/lee/ldf-card"); // inherited by all (future) queries
+	sparqler.addNamedGraph("http://torrez.us/elias/foaf.rdf");
+	sparqler.setPrefix("foaf", "http://xmlns.com/foaf/0.1/"); // inherited by all (future) queries
+	sparqler.setPrefix("rdf", "http://xmlns.com/foaf/0.1/");
+	
+	sparqler.setRequestHeader("Authentication", "Basic: " + basicAuthString);
+	
+	//sparqler.wantOutputAs("application/json"); // for now we only do JSON
+
+	var query = sparqler.createQuery();
+	query.addDefualtGraph(...) query.addNamedGraph(...) query.setPrefix(...) query.setRequestHeader(...) // this query only
+
+	// query forms:
+
+	// passes standard JSON results object to success callback
+	query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
+	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
+	query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
+	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
+	query.setPrefix("ldf", "http://thefigtrees.net/lee/ldf-card#");
+	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.<br>";
+		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<br>";
+			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 ... <br>";
+		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 + '&soft-limit=-1&';
+		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;
+}
+
+// Nothing to see here, yet.
+SPARQL.QueryUtilities = {
+};
+