Remove solitude auth check from monitor Bug 1311724

import os
import socket
import StringIO
import tempfile
import time
import traceback

from django.conf import settings

import commonware.log
import elasticsearch
import requests
from cache_nuggets.lib import memoize
from PIL import Image

from lib.crypto import packaged, receipt
from lib.crypto.packaged import SigningError as PackageSigningError
from lib.crypto.receipt import SigningError
from lib.pay_server import client
from mkt.site.storage_utils import local_storage

monitor_log = commonware.log.getLogger('z.monitor')

def memcache():
    memcache = getattr(settings, 'CACHES', {}).get('default')
    memcache_results = []
    status = ''
    if memcache and 'memcache' in memcache['BACKEND']:
        hosts = memcache['LOCATION']
        using_twemproxy = False
        if not isinstance(hosts, (tuple, list)):
            hosts = [hosts]
        for host in hosts:
            ip, port = host.split(':')

            if ip == '127.0.0.1':
                using_twemproxy = True

            try:
                s = socket.socket()
                s.connect((ip, int(port)))
            except Exception, e:
                result = False
                status = 'Failed to connect to memcached (%s): %s' % (host, e)
                monitor_log.critical(status)
            else:
                result = True
            finally:
                s.close()

            memcache_results.append((ip, port, result))
        if (not using_twemproxy and len(hosts) > 1 and
                len(memcache_results) < 2):
            # If the number of requested hosts is greater than 1, but less
            # than 2 replied, raise an error.
            status = ('2+ memcache servers are required.'
                      '%s available') % len(memcache_results)
            monitor_log.warning(status)

    # If we are in debug mode, don't worry about checking for memcache.
    elif settings.DEBUG:
        return status, []

    if not memcache_results:
        status = 'Memcache is not configured'
        monitor_log.info(status)

    return status, memcache_results

def libraries():
    # Check Libraries and versions
    libraries_results = []
    status = ''
    try:
        Image.new('RGB', (16, 16)).save(StringIO.StringIO(), 'JPEG')
        libraries_results.append(('PIL+JPEG', True, 'Got it!'))
    except Exception, e:
        msg = "Failed to create a jpeg image: %s" % e
        libraries_results.append(('PIL+JPEG', False, msg))

    try:
        import M2Crypto  # NOQA
        libraries_results.append(('M2Crypto', True, 'Got it!'))
    except ImportError:
        libraries_results.append(('M2Crypto', False, 'Failed to import'))

    if settings.SPIDERMONKEY:
        if os.access(settings.SPIDERMONKEY, os.R_OK):
            libraries_results.append(('Spidermonkey is ready!', True, None))
            # TODO: see if it works?
        else:
            msg = "You said spidermonkey was at (%s)" % settings.SPIDERMONKEY
            libraries_results.append(('Spidermonkey', False, msg))
    # If settings are debug and spidermonkey is empty,
    # thorw this error.
    elif settings.DEBUG and not settings.SPIDERMONKEY:
        msg = 'SPIDERMONKEY is empty'
        libraries_results.append(('Spidermonkey', True, msg))
    else:
        msg = "Please set SPIDERMONKEY in your settings file."
        libraries_results.append(('Spidermonkey', False, msg))

    missing_libs = [l for l, s, m in libraries_results if not s]
    if missing_libs:
        status = 'missing libs: %s' % ",".join(missing_libs)
    return status, libraries_results

def elastic():
    es = elasticsearch.Elasticsearch(hosts=settings.ES_HOSTS)
    elastic_results = None
    status = ''
    try:
        health = es.cluster.health()
        if health['status'] == 'red':
            status = 'ES is red'
        elastic_results = health
    except elasticsearch.ElasticsearchException:
        monitor_log.exception('Failed to communicate with ES')
        elastic_results = {'error': traceback.format_exc()}
        status = 'traceback'

    return status, elastic_results

def path():
    # Check file paths / permissions
    rw = (settings.TMP_PATH,
          settings.NETAPP_STORAGE,
          settings.UPLOADS_PATH,
          settings.ADDONS_PATH,
          settings.GUARDED_ADDONS_PATH,
          settings.ADDON_ICONS_PATH,
          settings.WEBSITE_ICONS_PATH,
          settings.PREVIEWS_PATH,
          settings.REVIEWER_ATTACHMENTS_PATH,)
    r = [os.path.join(settings.ROOT, 'locale')]
    filepaths = [(path, os.R_OK | os.W_OK, "We want read + write")
                 for path in rw]
    filepaths += [(path, os.R_OK, "We want read") for path in r]
    filepath_results = []
    filepath_status = True

    for path, perms, notes in filepaths:
        path_exists = os.path.exists(path)
        path_perms = os.access(path, perms)
        filepath_status = filepath_status and path_exists and path_perms
        filepath_results.append((path, path_exists, path_perms, notes))

    key_exists = os.path.exists(settings.WEBAPPS_RECEIPT_KEY)
    key_perms = os.access(settings.WEBAPPS_RECEIPT_KEY, os.R_OK)
    filepath_status = filepath_status and key_exists and key_perms
    filepath_results.append(('settings.WEBAPPS_RECEIPT_KEY',
                             key_exists, key_perms, 'We want read'))

    status = filepath_status
    status = ''
    if not filepath_status:
        status = 'check main status page for broken perms'

    return status, filepath_results

# The signer check actually asks the signing server to sign something. Do this
# once per nagios check, once per web head might be a bit much. The memoize
# slows it down a bit, by caching the result for 15 seconds.
@memoize('monitors-signer', time=15)
def receipt_signer():
    destination = getattr(settings, 'SIGNING_SERVER', None)
    if not destination:
        return '', 'Signer is not configured.'

    # Just send some test data into the signer.
    now = int(time.time())
    not_valid = (settings.SITE_URL + '/not-valid')
    data = {'detail': not_valid, 'exp': now + 3600, 'iat': now,
            'iss': settings.SITE_URL,
            'product': {'storedata': 'id=1', 'url': u'http://not-valid.com'},
            'nbf': now, 'typ': 'purchase-receipt',
            'reissue': not_valid,
            'user': {'type': 'directed-identifier',
                     'value': u'something-not-valid'},
            'verify': not_valid
            }

    try:
        result = receipt.sign(data)
    except SigningError as err:
        msg = 'Error on signing (%s): %s' % (destination, err)
        return msg, msg

    try:
        cert, rest = receipt.crack(result)
    except Exception as err:
        msg = 'Error on cracking receipt (%s): %s' % (destination, err)
        return msg, msg

    # Check that the certs used to sign the receipts are not about to expire.
    limit = now + (60 * 60 * 24)  # One day.
    if cert['exp'] < limit:
        msg = 'Cert will expire soon (%s)' % destination
        return msg, msg

    cert_err_msg = 'Error on checking public cert (%s): %s'
    location = cert['iss']
    try:
        resp = requests.get(location, timeout=5, stream=False)
    except Exception as err:
        msg = cert_err_msg % (location, err)
        return msg, msg

    if not resp.ok:
        msg = cert_err_msg % (location, resp.reason)
        return msg, msg

    cert_json = resp.json()
    if not cert_json or 'jwk' not in cert_json:
        msg = cert_err_msg % (location, 'Not valid JSON/JWK')
        return msg, msg

    return '', 'Signer working and up to date'

# Like the receipt signer above this asks the packaged app signing
# service to sign one for us.
@memoize('monitors-package-signer', time=60)
def package_signer():
    destination = getattr(settings, 'SIGNED_APPS_SERVER', None)
    if not destination:
        return '', 'Signer is not configured.'
    app_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            'nagios_check_packaged_app.zip')
    signed_path = tempfile.mktemp()
    try:
        packaged.sign_app(local_storage.open(app_path), signed_path, None,
                          False, local=True)
        return '', 'Package signer working'
    except PackageSigningError, e:
        msg = 'Error on package signing (%s): %s' % (destination, e)
        return msg, msg
    finally:
        local_storage.delete(signed_path)

# Not called settings to avoid conflict with django.conf.settings.
def settings_check():
    required = ['APP_PURCHASE_KEY', 'APP_PURCHASE_TYP', 'APP_PURCHASE_AUD',
                'APP_PURCHASE_SECRET']
    for key in required:
        if not getattr(settings, key):
            msg = 'Missing required value %s' % key
            return msg, msg

    return '', 'Required settings ok'

def solitude():
    try:
        res = client.api.services.request.get()
    except Exception as err:
        return repr(err), repr(err)
    auth = res.get('authenticated', None)
    if auth != 'marketplace':
        msg = 'Solitude authenticated as: %s' % auth
        return msg, msg

    return '', 'Solitude authentication ok'

import os
import socket
import StringIO
import tempfile
import time
import traceback

from django.conf import settings

import commonware.log
import elasticsearch
import requests
from cache_nuggets.lib import memoize
from PIL import Image

from lib.crypto import packaged, receipt
from lib.crypto.packaged import SigningError as PackageSigningError
from lib.crypto.receipt import SigningError
from mkt.site.storage_utils import local_storage

monitor_log = commonware.log.getLogger('z.monitor')

def memcache():
    memcache = getattr(settings, 'CACHES', {}).get('default')
    memcache_results = []
    status = ''
    if memcache and 'memcache' in memcache['BACKEND']:
        hosts = memcache['LOCATION']
        using_twemproxy = False
        if not isinstance(hosts, (tuple, list)):
            hosts = [hosts]
        for host in hosts:
            ip, port = host.split(':')

            if ip == '127.0.0.1':
                using_twemproxy = True

            try:
                s = socket.socket()
                s.connect((ip, int(port)))
            except Exception, e:
                result = False
                status = 'Failed to connect to memcached (%s): %s' % (host, e)
                monitor_log.critical(status)
            else:
                result = True
            finally:
                s.close()

            memcache_results.append((ip, port, result))
        if (not using_twemproxy and len(hosts) > 1 and
                len(memcache_results) < 2):
            # If the number of requested hosts is greater than 1, but less
            # than 2 replied, raise an error.
            status = ('2+ memcache servers are required.'
                      '%s available') % len(memcache_results)
            monitor_log.warning(status)

    # If we are in debug mode, don't worry about checking for memcache.
    elif settings.DEBUG:
        return status, []

    if not memcache_results:
        status = 'Memcache is not configured'
        monitor_log.info(status)

    return status, memcache_results

def libraries():
    # Check Libraries and versions
    libraries_results = []
    status = ''
    try:
        Image.new('RGB', (16, 16)).save(StringIO.StringIO(), 'JPEG')
        libraries_results.append(('PIL+JPEG', True, 'Got it!'))
    except Exception, e:
        msg = "Failed to create a jpeg image: %s" % e
        libraries_results.append(('PIL+JPEG', False, msg))

    try:
        import M2Crypto  # NOQA
        libraries_results.append(('M2Crypto', True, 'Got it!'))
    except ImportError:
        libraries_results.append(('M2Crypto', False, 'Failed to import'))

    if settings.SPIDERMONKEY:
        if os.access(settings.SPIDERMONKEY, os.R_OK):
            libraries_results.append(('Spidermonkey is ready!', True, None))
            # TODO: see if it works?
        else:
            msg = "You said spidermonkey was at (%s)" % settings.SPIDERMONKEY
            libraries_results.append(('Spidermonkey', False, msg))
    # If settings are debug and spidermonkey is empty,
    # thorw this error.
    elif settings.DEBUG and not settings.SPIDERMONKEY:
        msg = 'SPIDERMONKEY is empty'
        libraries_results.append(('Spidermonkey', True, msg))
    else:
        msg = "Please set SPIDERMONKEY in your settings file."
        libraries_results.append(('Spidermonkey', False, msg))

    missing_libs = [l for l, s, m in libraries_results if not s]
    if missing_libs:
        status = 'missing libs: %s' % ",".join(missing_libs)
    return status, libraries_results

def elastic():
    es = elasticsearch.Elasticsearch(hosts=settings.ES_HOSTS)
    elastic_results = None
    status = ''
    try:
        health = es.cluster.health()
        if health['status'] == 'red':
            status = 'ES is red'
        elastic_results = health
    except elasticsearch.ElasticsearchException:
        monitor_log.exception('Failed to communicate with ES')
        elastic_results = {'error': traceback.format_exc()}
        status = 'traceback'

    return status, elastic_results

def path():
    # Check file paths / permissions
    rw = (settings.TMP_PATH,
          settings.NETAPP_STORAGE,
          settings.UPLOADS_PATH,
          settings.ADDONS_PATH,
          settings.GUARDED_ADDONS_PATH,
          settings.ADDON_ICONS_PATH,
          settings.WEBSITE_ICONS_PATH,
          settings.PREVIEWS_PATH,
          settings.REVIEWER_ATTACHMENTS_PATH,)
    r = [os.path.join(settings.ROOT, 'locale')]
    filepaths = [(path, os.R_OK | os.W_OK, "We want read + write")
                 for path in rw]
    filepaths += [(path, os.R_OK, "We want read") for path in r]
    filepath_results = []
    filepath_status = True

    for path, perms, notes in filepaths:
        path_exists = os.path.exists(path)
        path_perms = os.access(path, perms)
        filepath_status = filepath_status and path_exists and path_perms
        filepath_results.append((path, path_exists, path_perms, notes))

    key_exists = os.path.exists(settings.WEBAPPS_RECEIPT_KEY)
    key_perms = os.access(settings.WEBAPPS_RECEIPT_KEY, os.R_OK)
    filepath_status = filepath_status and key_exists and key_perms
    filepath_results.append(('settings.WEBAPPS_RECEIPT_KEY',
                             key_exists, key_perms, 'We want read'))

    status = filepath_status
    status = ''
    if not filepath_status:
        status = 'check main status page for broken perms'

    return status, filepath_results

# The signer check actually asks the signing server to sign something. Do this
# once per nagios check, once per web head might be a bit much. The memoize
# slows it down a bit, by caching the result for 15 seconds.
@memoize('monitors-signer', time=15)
def receipt_signer():
    destination = getattr(settings, 'SIGNING_SERVER', None)
    if not destination:
        return '', 'Signer is not configured.'

    # Just send some test data into the signer.
    now = int(time.time())
    not_valid = (settings.SITE_URL + '/not-valid')
    data = {'detail': not_valid, 'exp': now + 3600, 'iat': now,
            'iss': settings.SITE_URL,
            'product': {'storedata': 'id=1', 'url': u'http://not-valid.com'},
            'nbf': now, 'typ': 'purchase-receipt',
            'reissue': not_valid,
            'user': {'type': 'directed-identifier',
                     'value': u'something-not-valid'},
            'verify': not_valid
            }

    try:
        result = receipt.sign(data)
    except SigningError as err:
        msg = 'Error on signing (%s): %s' % (destination, err)
        return msg, msg

    try:
        cert, rest = receipt.crack(result)
    except Exception as err:
        msg = 'Error on cracking receipt (%s): %s' % (destination, err)
        return msg, msg

    # Check that the certs used to sign the receipts are not about to expire.
    limit = now + (60 * 60 * 24)  # One day.
    if cert['exp'] < limit:
        msg = 'Cert will expire soon (%s)' % destination
        return msg, msg

    cert_err_msg = 'Error on checking public cert (%s): %s'
    location = cert['iss']
    try:
        resp = requests.get(location, timeout=5, stream=False)
    except Exception as err:
        msg = cert_err_msg % (location, err)
        return msg, msg

    if not resp.ok:
        msg = cert_err_msg % (location, resp.reason)
        return msg, msg

    cert_json = resp.json()
    if not cert_json or 'jwk' not in cert_json:
        msg = cert_err_msg % (location, 'Not valid JSON/JWK')
        return msg, msg

    return '', 'Signer working and up to date'

# Like the receipt signer above this asks the packaged app signing
# service to sign one for us.
@memoize('monitors-package-signer', time=60)
def package_signer():
    destination = getattr(settings, 'SIGNED_APPS_SERVER', None)
    if not destination:
        return '', 'Signer is not configured.'
    app_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            'nagios_check_packaged_app.zip')
    signed_path = tempfile.mktemp()
    try:
        packaged.sign_app(local_storage.open(app_path), signed_path, None,
                          False, local=True)
        return '', 'Package signer working'
    except PackageSigningError, e:
        msg = 'Error on package signing (%s): %s' % (destination, e)
        return msg, msg
    finally:
        local_storage.delete(signed_path)

# Not called settings to avoid conflict with django.conf.settings.
def settings_check():
    required = ['APP_PURCHASE_KEY', 'APP_PURCHASE_TYP', 'APP_PURCHASE_AUD',
                'APP_PURCHASE_SECRET']
    for key in required:
        if not getattr(settings, key):
            msg = 'Missing required value %s' % key
            return msg, msg

    return '', 'Required settings ok'


Configurable SNORQL instance and webjar creation

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

    <title>Snorql: A SPARQL Explorer</title>
    <link rel="stylesheet" type="text/css" href="style.css" />

Snorql: A SPARQL Explorer

Browse:

Super Classes

Classes

Properties

Named Graphs

GRAPH:

        Default graph.
        List named graphs

GRAPH:

        Named graph goes here.
        Switch back to default graph

SPARQL:

      <form id="queryform" action="#" method="get">

        <input type="hidden" name="query" value="" id="query" />
        <input type="hidden" name="output" value="json" id="jsonoutput" disabled="disabled" />
        <input type="hidden" name="stylesheet" value="" id="stylesheet" disabled="disabled" />
        <input type="hidden" name="graph" value="" id="graph-uri" disabled="disabled" />

</form>

        <textarea name="query" rows="9" cols="80" id="querytext"></textarea>
        Results:
        <select id="selectoutput" onchange="snorql.updateOutputMode()">
          <option selected="selected" value="browse">Browse</option>
          <option value="json">as JSON</option>
          <option value="xml">as XML</option>
          <option value="xslt">as XML+XSLT</option>
        </select>

          XSLT stylesheet URL:
          <input id="xsltstylesheet" type="text" value="snorql/xml-to-html.xsl" size="30" />

        <input type="button" value="Go!" onclick="snorql.submitQuery()" />
        <input type="button" value="Reset" onclick="snorql.resetQuery()" />

Powered by Snorql

var snorql = new Snorql();

String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

String.prototype.startsWith = function(str) {
	return (this.match("^"+str) == str);
}

function Snorql() {
    // modify this._endpoint to point to your SPARQL endpoint
    this._endpoint = document.location.href.match(/^([^?]*)snorql\//)[1] + 'sparql';
    // modify these to your likeing
    this._poweredByLink = 'http://www4.wiwiss.fu-berlin.de/bizer/d2r-server/';
    this._poweredByLabel = 'D2R Server';
    this._enableNamedGraphs = false;

    this._browserBase = null;
    this._namespaces = {};
    this._graph = null;
    this._xsltDOM = null;

    this.start = function() {
        // TODO: Extract a QueryType class
        this.setBrowserBase(document.location.href.replace(/\?.*/, ''));
        this._displayEndpointURL();
        this._displayPoweredBy();
        this.setNamespaces(D2R_namespacePrefixes);
        this.updateOutputMode();
        var match = document.location.href.match(/\?(.*)/);
        var queryString = match ? match[1] : '';
        if (!queryString) {
            document.getElementById('querytext').value = 'SELECT DISTINCT * WHERE {\n  ?s ?p ?o\n}\nLIMIT 10';
            this._updateGraph(null, false);
            return;
        }
        var graph = queryString.match(/graph=([^&]*)/);
        graph = graph ? decodeURIComponent(graph[1]) : null;
        this._updateGraph(graph, false);
        var browse = queryString.match(/browse=([^&]*)/);
        var querytext = null;
        if (browse && browse[1] == 'superclasses') {
            var resultTitle = 'List of all super classes:';
            var querytext = 'SELECT DISTINCT ?class\n' +
                    'WHERE { [] rdfs:subClassOf ?class }\n' +
                    'ORDER BY ?class';
            var query = 'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n' + querytext;
        }
        if (browse && browse[1] == 'classes') {
            var resultTitle = 'List of all classes:';
            var query = 'SELECT DISTINCT ?class\n' +
                    'WHERE { [] a ?class }\n' +
                    'ORDER BY ?class';
        }
        if (browse && browse[1] == 'properties') {
            var resultTitle = 'List of all properties:';
            var query = 'SELECT DISTINCT ?property\n' +
                    'WHERE { [] ?property [] }\n' +
                    'ORDER BY ?property';
        }
        if (browse && browse[1] == 'graphs') {
            var resultTitle = 'List of all named graphs:';
            var querytext = 'SELECT DISTINCT ?namedgraph ?label\n' +
                    'WHERE {\n' +
                    '  GRAPH ?namedgraph { ?s ?p ?o }\n' +
                    '  OPTIONAL { ?namedgraph rdfs:label ?label }\n' +
                    '}\n' +
                    'ORDER BY ?namedgraph';
            var query = 'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n' + querytext;
        }
        var match = queryString.match(/property=([^&]*)/);
        if (match) {
            var resultTitle = 'All uses of property ' + decodeURIComponent(match[1]) + ':';
            var query = 'SELECT DISTINCT ?resource ?value\n' +
                    'WHERE { ?resource <' + decodeURIComponent(match[1]) + '> ?value }\n' +
                    'ORDER BY ?resource ?value';
        }
        var match = queryString.match(/class=([^&]*)/);
        if (match) {
            var resultTitle = 'All instances of class ' + decodeURIComponent(match[1]) + ':';
            var query = 'SELECT DISTINCT ?instance\n' +
                    'WHERE { ?instance a <' + decodeURIComponent(match[1]) + '> }\n' +
                    'ORDER BY ?instance';
        }
        var match = queryString.match(/describe=([^&]*)/);
        if (match) {
            var resultTitle = 'Description of ' + decodeURIComponent(match[1]) + ':';
            var query = 'SELECT DISTINCT ?property ?hasValue ?isValueOf\n' +
                    'WHERE {\n' +
                    '  { <' + decodeURIComponent(match[1]) + '> ?property ?hasValue }\n' +
                    '  UNION\n' +
                    '  { ?isValueOf ?property <' + decodeURIComponent(match[1]) + '> }\n' +
                    '}\n' +
                    'ORDER BY (!BOUND(?hasValue)) ?property ?hasValue ?isValueOf';
        }
        if (queryString.match(/query=/)) {
            var resultTitle = 'SPARQL results:';
            querytext = this._betterUnescape(queryString.match(/query=([^&]*)/)[1]);
            var query = prefixes + querytext;
        }
        if (!querytext) {
            querytext = query;
        }
        document.getElementById('querytext').value = querytext;
        this.displayBusyMessage();
        var service = new SPARQL.Service(this._endpoint);
        if (this._graph) {
            service.addDefaultGraph(this._graph);
        }

        // AndyL changed MIME type and success callback depending on query form...
        var dummy = this;

   	    var exp = /^\s*(?:PREFIX\s+\w*:\s+<[^>]*>\s*)*(\w+)\s*.*/i;
   	    var match = exp.exec(querytext);
   	    if (match) {
	        if (match[1].toUpperCase() == 'ASK') {
	        	service.setOutput('boolean');
	        	var successFunc = function(value) {
	                dummy.displayBooleanResult(value, resultTitle);
	            };
	        } else if (match[1].toUpperCase() == 'CONSTRUCT' || match[1].toUpperCase() == 'DESCRIBE'){ // construct describe
	    		service.setOutput('rdf'); // !json
	    		var successFunc = function(model) {
	                dummy.displayRDFResult(model, resultTitle);
	            };
	        } else {
	        	service.setRequestHeader('Accept', 'application/sparql-results+json,*/*');
	        	service.setOutput('json');
	        	var successFunc = function(json) {
	        		dummy.displayJSONResult(json, resultTitle);
	        	};
	        }
   	    }

        service.query(query, {
            success: successFunc,
            failure: function(report) {
                var message = report.responseText.match(/<pre>([\s\S]*)<\/pre>/);
                if (message) {
                    dummy.displayErrorMessage(message[1]);
                } else {
                    dummy.displayErrorMessage(report.responseText);
                }
            }
        });
    }

    this.setBrowserBase = function(url) {
        this._browserBase = url;
    }

    this._displayEndpointURL = function() {
        var newTitle = 'Snorql: Exploring ' + this._endpoint;
        this._display(document.createTextNode(newTitle), 'title');
        document.title = newTitle;
    }

    this._displayPoweredBy = function() {
        $('poweredby').href = this._poweredByLink;
        $('poweredby').update(this._poweredByLabel);
    }

    this.setNamespaces = function(namespaces) {
        this._namespaces = namespaces;
        this._display(document.createTextNode(this._getPrefixes()), 'prefixestext');
    }

    this.switchToGraph = function(uri) {
        this._updateGraph(uri, true);
    }

    this.switchToDefaultGraph = function() {
        this._updateGraph(null, true);
    }

    this._updateGraph = function(uri, effect) {
        if (!this._enableNamedGraphs) {
            $('default-graph-section').hide();
            $('named-graph-section').hide();
            $('browse-named-graphs-link').hide();
            return;
        }
        var changed = (uri != this._graph);
        this._graph = uri;
        var el = document.getElementById('graph-uri');
        el.disabled = (this._graph == null);
        el.value = this._graph;
        if (this._graph == null) {
            var show = 'default-graph-section';
            var hide = 'named-graph-section';
            $$('a.graph-link').each(function(link) {
                match = link.href.match(/^(.*)[&?]graph=/);
                if (match) link.href = match[1];
            });
        } else {
            var show = 'named-graph-section';
            var hide = 'default-graph-section';
            $('selected-named-graph').update(this._graph);
            var uri = this._graph;
            $$('a.graph-link').each(function(link) {
                match = link.href.match(/^(.*)[&?]graph=/);
                if (!match) link.href = link.href + '&graph=' + uri;
            });
        }
        $(hide).hide();
        $(show).show();
        if (effect && changed) {
            new Effect.Highlight(show,
                {startcolor: '#ffff00', endcolor: '#ccccff', resotrecolor: '#ccccff'});
        }
        $('graph-uri').disabled = (this._graph == null);
        $('graph-uri').value = this._graph;
    }

    this.updateOutputMode = function() {
        if (this._xsltDOM == null) {
            this._xsltDOM = document.getElementById('xsltinput');
        }
        var el = document.getElementById('xsltcontainer');
        while (el.childNodes.length > 0) {
            el.removeChild(el.firstChild);
        }
        if (this._selectedOutputMode() == 'xslt') {
            el.appendChild(this._xsltDOM);
        }
    }

    this.resetQuery = function() {
        document.location = this._browserBase;
    }

    this.submitQuery = function() {
        var mode = this._selectedOutputMode();
        if (mode == 'browse') {
            document.getElementById('queryform').action = this._browserBase;
            document.getElementById('query').value = document.getElementById('querytext').value;
        } else {
            document.getElementById('query').value = this._getPrefixes() + document.getElementById('querytext').value;
            document.getElementById('queryform').action = this._endpoint;
        }
        document.getElementById('jsonoutput').disabled = (mode != 'json');
        document.getElementById('stylesheet').disabled = (mode != 'xslt' || !document.getElementById('xsltstylesheet').value);
        if (mode == 'xslt') {
            document.getElementById('stylesheet').value = document.getElementById('xsltstylesheet').value;
        }
        document.getElementById('queryform').submit();
    }

    this.displayBusyMessage = function() {
        var busy = document.createElement('div');
        busy.className = 'busy';
        busy.appendChild(document.createTextNode('Executing query ...'));
        this._display(busy, 'result');
    }

    this.displayErrorMessage = function(message) {
        var pre = document.createElement('pre');
        pre.innerHTML = message;
        this._display(pre, 'result');
    }

    this.displayBooleanResult = function(value, resultTitle) {
        var div = document.createElement('div');
        var title = document.createElement('h2');
        title.appendChild(document.createTextNode(resultTitle));
        div.appendChild(title);
        if (value)
        	div.appendChild(document.createTextNode("TRUE"));
        else
        	div.appendChild(document.createTextNode("FALSE"));
        this._display(div, 'result');
        this._updateGraph(this._graph); // refresh links in new result
    }

    this.displayRDFResult = function(model, resultTitle) {
        var div = document.createElement('div');
        var title = document.createElement('h2');
        title.appendChild(document.createTextNode(resultTitle));
        div.appendChild(title);
        div.appendChild(new RDFXMLFormatter(model));
        this._display(div, 'result');
        this._updateGraph(this._graph); // refresh links in new result - necessary for boolean?
    }

    this.displayJSONResult = function(json, resultTitle) {
        var div = document.createElement('div');
        var title = document.createElement('h2');
        title.appendChild(document.createTextNode(resultTitle));
        div.appendChild(title);
        if (json.results.bindings.length == 0) {
            var p = document.createElement('p');
            p.className = 'empty';
            p.appendChild(document.createTextNode('[no results]'));
            div.appendChild(p);
        } else {
            div.appendChild(new SPARQLResultFormatter(json, this._namespaces).toDOM());
        }
        this._display(div, 'result');
        this._updateGraph(this._graph); // refresh links in new result
    }

    this._display = function(node, whereID) {
        var where = document.getElementById(whereID);
        if (!where) {
            alert('ID not found: ' + whereID);
            return;
        }
        while (where.firstChild) {
            where.removeChild(where.firstChild);
        }
        if (node == null) return;
        where.appendChild(node);
    }

    this._selectedOutputMode = function() {
        return document.getElementById('selectoutput').value;
    }

    this._getPrefixes = function() {
        prefixes = '';
        for (prefix in this._namespaces) {
            var uri = this._namespaces[prefix];
            prefixes = prefixes + 'PREFIX ' + prefix + ': <' + uri + '>\n';
        }
        return prefixes;
    }

    this._betterUnescape = function(s) {
        return unescape(s.replace(/\+/g, ' '));
    }
}

/*
 * RDFXMLFormatter
 *
 * maybe improve...
 */
function RDFXMLFormatter(string) {
	var pre = document.createElement('pre');
	pre.appendChild(document.createTextNode(string));
	return pre;
}

/*
===========================================================================
SPARQLResultFormatter: Renders a SPARQL/JSON result set into an HTML table.

var namespaces = { 'xsd': '', 'foaf': 'http://xmlns.com/foaf/0.1' };
var formatter = new SPARQLResultFormatter(json, namespaces);
var tableObject = formatter.toDOM();
*/
function SPARQLResultFormatter(json, namespaces) {
    this._json = json;
    this._variables = this._json.head.vars;
    this._results = this._json.results.bindings;
    this._namespaces = namespaces;

    this.toDOM = function() {
        var table = document.createElement('table');
        table.className = 'queryresults';
        table.appendChild(this._createTableHeader());
        for (var i = 0; i < this._results.length; i++) {
            table.appendChild(this._createTableRow(this._results[i], i));
        }
        return table;
    }

    // TODO: Refactor; non-standard link makers should be passed into the class by the caller
    this._getLinkMaker = function(varName) {
        if (varName == 'property') {
            return function(uri) { return '?property=' + encodeURIComponent(uri); };
        } else if (varName == 'class') {
            return function(uri) { return '?class=' + encodeURIComponent(uri); };
        } else {
            return function(uri) { return '?describe=' + encodeURIComponent(uri); };
        }
    }

    this._createTableHeader = function() {
        var tr = document.createElement('tr');
        var hasNamedGraph = false;
        for (var i = 0; i < this._variables.length; i++) {
            var th = document.createElement('th');
            th.appendChild(document.createTextNode(this._variables[i]));
            tr.appendChild(th);
            if (this._variables[i] == 'namedgraph') {
                hasNamedGraph = true;
            }
        }
        if (hasNamedGraph) {
            var th = document.createElement('th');
            th.appendChild(document.createTextNode(' '));
            tr.insertBefore(th, tr.firstChild);
        }
        return tr;
    }

    this._createTableRow = function(binding, rowNumber) {
        var tr = document.createElement('tr');
        if (rowNumber % 2) {
            tr.className = 'odd';
        } else {
            tr.className = 'even';
        }
        var namedGraph = null;
        for (var i = 0; i < this._variables.length; i++) {
            var varName = this._variables[i];
            td = document.createElement('td');
            td.appendChild(this._formatNode(binding[varName], varName));
            tr.appendChild(td);
            if (this._variables[i] == 'namedgraph') {
                namedGraph = binding[varName];
            }
        }
        if (namedGraph) {
            var link = document.createElement('a');
            link.href = 'javascript:snorql.switchToGraph(\'' + namedGraph.value + '\')';
            link.appendChild(document.createTextNode('Switch'));
            var td = document.createElement('td');
            td.appendChild(link);
            tr.insertBefore(td, tr.firstChild);
        }
        return tr;
    }

    this._formatNode = function(node, varName) {
        if (!node) {
            return this._formatUnbound(node, varName);
        }
        if (node.type == 'uri') {
            return this._formatURI(node, varName);
        }
        if (node.type == 'bnode') {
            return this._formatBlankNode(node, varName);
        }
        if (node.type == 'literal') {
            return this._formatPlainLiteral(node, varName);
        }
        if (node.type == 'typed-literal') {
            return this._formatTypedLiteral(node, varName);
        }
        return document.createTextNode('???');
    }

    this._formatURI = function(node, varName) {
        var span = document.createElement('span');
        span.className = 'uri';
        var a = document.createElement('a');
        a.href = this._getLinkMaker(varName)(node.value);
        a.title = '<' + node.value + '>';
        a.className = 'graph-link';
        var qname = this._toQName(node.value);
        if (qname) {
            a.appendChild(document.createTextNode(qname));
            span.appendChild(a);
        } else {
            a.appendChild(document.createTextNode(node.value));
            span.appendChild(document.createTextNode('<'));
            span.appendChild(a);
            span.appendChild(document.createTextNode('>'));
        }
        match = node.value.match(/^(https?|ftp|mailto|irc|gopher|news):/);
        if (match) {
            span.appendChild(document.createTextNode(' '));
            var externalLink = document.createElement('a');
            externalLink.href = node.value;
            img = document.createElement('img');
            img.src = 'link.png';
            img.alt = '[' + match[1] + ']';
            img.title = 'Go to Web page';
            externalLink.appendChild(img);
            span.appendChild(externalLink);
        }
        return span;
    }

    this._formatPlainLiteral = function(node, varName) {
        var text = '"' + node.value + '"';
        if (node['xml:lang']) {
            text += '@' + node['xml:lang'];
        }
        return document.createTextNode(text);
    }

    this._formatTypedLiteral = function(node, varName) {
        var text = '"' + node.value + '"';
        if (node.datatype) {
            text += '^^' + this._toQNameOrURI(node.datatype);
        }
        if (this._isNumericXSDType(node.datatype)) {
            var span = document.createElement('span');
            span.title = text;
            span.appendChild(document.createTextNode(node.value));
            return span;
        }
        return document.createTextNode(text);
    }

    this._formatBlankNode = function(node, varName) {
        return document.createTextNode('_:' + node.value);
    }

    this._formatUnbound = function(node, varName) {
        var span = document.createElement('span');
        span.className = 'unbound';
        span.title = 'Unbound'
        span.appendChild(document.createTextNode('-'));
        return span;
    }

    this._toQName = function(uri) {
        for (prefix in this._namespaces) {
            var nsURI = this._namespaces[prefix];
            if (uri.indexOf(nsURI) == 0) {
                return prefix + ':' + uri.substring(nsURI.length);
            }
        }
        return null;
    }

    this._toQNameOrURI = function(uri) {
        var qName = this._toQName(uri);
        return (qName == null) ? '<' + uri + '>' : qName;
    }

    this._isNumericXSDType = function(datatypeURI) {
        for (i = 0; i < this._numericXSDTypes.length; i++) {
            if (datatypeURI == this._xsdNamespace + this._numericXSDTypes[i]) {
                return true;
            }
        }
        return false;
    }
    this._xsdNamespace = 'http://www.w3.org/2001/XMLSchema#';
    this._numericXSDTypes = ['long', 'decimal', 'float', 'double', 'int',
        'short', 'byte', 'integer', 'nonPositiveInteger', 'negativeInteger',
        'nonNegativeInteger', 'positiveInteger', 'unsignedLong',
        'unsignedInt', 'unsignedShort', 'unsignedByte'];
}

String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

String.prototype.startsWith = function(str) {
	return (this.match("^"+str) == str);
}

function Snorql(options) {
    options = options || {};

    this._endpoint = options['endpoint'] || undefined;

    this._poweredByLink = options['poweredByLink'] || 'http://www4.wiwiss.fu-berlin.de/bizer/d2r-server/';
    this._poweredByLabel = options['poweredByLabel'] || 'D2R Server';
    this._enableNamedGraphs = options['enableNamedGraphs'] || false;

    this._browserBase = options['browserBase'] || null;
    this._namespaces = options['namespaces'] || D2R_namespacePrefixes;
    this._graph = options['graph'] || null;
    this._xsltDOM = options['xsltDOM'] || null;

    this.start = function() {
        // TODO: Extract a QueryType class
        this.setBrowserBase(document.location.href.replace(/\?.*/, ''));
        this._displayEndpointURL();
        this._displayPoweredBy();
        this.setNamespaces(this._namespaces);
        this.updateOutputMode();
        var match = document.location.href.match(/\?(.*)/);
        var queryString = match ? match[1] : '';
        if (!queryString) {
            document.getElementById('querytext').value = 'SELECT DISTINCT * WHERE {\n  ?s ?p ?o\n}\nLIMIT 10';
            this._updateGraph(null, false);
            return;
        }
        var graph = queryString.match(/graph=([^&]*)/);
        graph = graph ? decodeURIComponent(graph[1]) : null;
        this._updateGraph(graph, false);
        var browse = queryString.match(/browse=([^&]*)/);
        var querytext = null;
        if (browse && browse[1] == 'superclasses') {
            var resultTitle = 'List of all super classes:';
            var querytext = 'SELECT DISTINCT ?class\n' +
                    'WHERE { [] rdfs:subClassOf ?class }\n' +
                    'ORDER BY ?class';
            var query = 'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n' + querytext;
        }
        if (browse && browse[1] == 'classes') {
            var resultTitle = 'List of all classes:';
            var query = 'SELECT DISTINCT ?class\n' +
                    'WHERE { [] a ?class }\n' +
                    'ORDER BY ?class';
        }
        if (browse && browse[1] == 'properties') {
            var resultTitle = 'List of all properties:';
            var query = 'SELECT DISTINCT ?property\n' +
                    'WHERE { [] ?property [] }\n' +
                    'ORDER BY ?property';
        }
        if (browse && browse[1] == 'graphs') {
            var resultTitle = 'List of all named graphs:';
            var querytext = 'SELECT DISTINCT ?namedgraph ?label\n' +
                    'WHERE {\n' +
                    '  GRAPH ?namedgraph { ?s ?p ?o }\n' +
                    '  OPTIONAL { ?namedgraph rdfs:label ?label }\n' +
                    '}\n' +
                    'ORDER BY ?namedgraph';
            var query = 'PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n' + querytext;
        }
        var match = queryString.match(/property=([^&]*)/);
        if (match) {
            var resultTitle = 'All uses of property ' + decodeURIComponent(match[1]) + ':';
            var query = 'SELECT DISTINCT ?resource ?value\n' +
                    'WHERE { ?resource <' + decodeURIComponent(match[1]) + '> ?value }\n' +
                    'ORDER BY ?resource ?value';
        }
        var match = queryString.match(/class=([^&]*)/);
        if (match) {
            var resultTitle = 'All instances of class ' + decodeURIComponent(match[1]) + ':';
            var query = 'SELECT DISTINCT ?instance\n' +
                    'WHERE { ?instance a <' + decodeURIComponent(match[1]) + '> }\n' +
                    'ORDER BY ?instance';
        }
        var match = queryString.match(/describe=([^&]*)/);
        if (match) {
            var resultTitle = 'Description of ' + decodeURIComponent(match[1]) + ':';
            var query = 'SELECT DISTINCT ?property ?hasValue ?isValueOf\n' +
                    'WHERE {\n' +
                    '  { <' + decodeURIComponent(match[1]) + '> ?property ?hasValue }\n' +
                    '  UNION\n' +
                    '  { ?isValueOf ?property <' + decodeURIComponent(match[1]) + '> }\n' +
                    '}\n' +
                    'ORDER BY (!BOUND(?hasValue)) ?property ?hasValue ?isValueOf';
        }
        if (queryString.match(/query=/)) {
            var resultTitle = 'SPARQL results:';
            querytext = this._betterUnescape(queryString.match(/query=([^&]*)/)[1]);
            var query = prefixes + querytext;
        }
        if (!querytext) {
            querytext = query;
        }
        document.getElementById('querytext').value = querytext;
        this.displayBusyMessage();
        if (!this._endpoint) {
            this.displayErrorMessage("No SPARQL Endpoint provided!");
            return;
        }
        var service = new SPARQL.Service(this._endpoint);
        if (this._graph) {
            service.addDefaultGraph(this._graph);
        }

        // AndyL changed MIME type and success callback depending on query form...
        var dummy = this;

   	    var exp = /^\s*(?:PREFIX\s+\w*:\s+<[^>]*>\s*)*(\w+)\s*.*/i;
   	    var match = exp.exec(querytext);
   	    if (match) {
	        if (match[1].toUpperCase() == 'ASK') {
	        	service.setOutput('boolean');
	        	var successFunc = function(value) {
	                dummy.displayBooleanResult(value, resultTitle);
	            };
	        } else if (match[1].toUpperCase() == 'CONSTRUCT' || match[1].toUpperCase() == 'DESCRIBE'){ // construct describe
	    		service.setOutput('rdf'); // !json
	    		var successFunc = function(model) {
	                dummy.displayRDFResult(model, resultTitle);
	            };
	        } else {
	        	service.setRequestHeader('Accept', 'application/sparql-results+json,*/*');
	        	service.setOutput('json');
	        	var successFunc = function(json) {
	        		dummy.displayJSONResult(json, resultTitle);
	        	};
	        }
   	    }

        service.query(query, {
            success: successFunc,
            failure: function(report) {
                var message = report.responseText.match(/<pre>([\s\S]*)<\/pre>/);
                if (message) {
                    dummy.displayErrorMessage(message[1]);
                } else {
                    dummy.displayErrorMessage(report.responseText);
                }
            }
        });
    }

    this.setBrowserBase = function(url) {
        this._browserBase = url;
    }

    this._displayEndpointURL = function() {
        var newTitle = 'Snorql: Exploring ' + this._endpoint;
        this._display(document.createTextNode(newTitle), 'title');
        document.title = newTitle;
    }

    this._displayPoweredBy = function() {
        $('poweredby').href = this._poweredByLink;
        $('poweredby').update(this._poweredByLabel);
    }

    this.setNamespaces = function(namespaces) {
        this._namespaces = namespaces;
        this._display(document.createTextNode(this._getPrefixes()), 'prefixestext');
    }

    this.switchToGraph = function(uri) {
        this._updateGraph(uri, true);
    }

    this.switchToDefaultGraph = function() {
        this._updateGraph(null, true);
    }

    this._updateGraph = function(uri, effect) {
        if (!this._enableNamedGraphs) {
            $('default-graph-section').hide();
            $('named-graph-section').hide();
            $('browse-named-graphs-link').hide();
            return;
        }
        var changed = (uri != this._graph);
        this._graph = uri;
        var el = document.getElementById('graph-uri');
        el.disabled = (this._graph == null);
        el.value = this._graph;
        if (this._graph == null) {
            var show = 'default-graph-section';
            var hide = 'named-graph-section';
            $$('a.graph-link').each(function(link) {
                match = link.href.match(/^(.*)[&?]graph=/);
                if (match) link.href = match[1];
            });
        } else {
            var show = 'named-graph-section';
            var hide = 'default-graph-section';
            $('selected-named-graph').update(this._graph);
            var uri = this._graph;
            $$('a.graph-link').each(function(link) {
                match = link.href.match(/^(.*)[&?]graph=/);
                if (!match) link.href = link.href + '&graph=' + uri;
            });
        }
        $(hide).hide();
        $(show).show();
        if (effect && changed) {
            new Effect.Highlight(show,
                {startcolor: '#ffff00', endcolor: '#ccccff', resotrecolor: '#ccccff'});
        }
        $('graph-uri').disabled = (this._graph == null);
        $('graph-uri').value = this._graph;
    }

    this.updateOutputMode = function() {
        if (this._xsltDOM == null) {
            this._xsltDOM = document.getElementById('xsltinput');
        }
        var el = document.getElementById('xsltcontainer');
        while (el.childNodes.length > 0) {
            el.removeChild(el.firstChild);
        }
        if (this._selectedOutputMode() == 'xslt') {
            el.appendChild(this._xsltDOM);
        }
    }

    this.resetQuery = function() {
        document.location = this._browserBase;
    }

    this.submitQuery = function() {
        if (!this._endpoint) {
            this.displayErrorMessage("No SPARQL Endpoint defined!");
        }

        var mode = this._selectedOutputMode();
        if (mode == 'browse') {
            document.getElementById('queryform').action = this._browserBase;
            document.getElementById('query').value = document.getElementById('querytext').value;
        } else {
            document.getElementById('query').value = this._getPrefixes() + document.getElementById('querytext').value;
            document.getElementById('queryform').action = this._endpoint;
        }
        document.getElementById('jsonoutput').disabled = (mode != 'json');
        document.getElementById('stylesheet').disabled = (mode != 'xslt' || !document.getElementById('xsltstylesheet').value);
        if (mode == 'xslt') {
            document.getElementById('stylesheet').value = document.getElementById('xsltstylesheet').value;
        }
        document.getElementById('queryform').submit();
    }

    this.displayBusyMessage = function() {
        var busy = document.createElement('div');
        busy.className = 'busy';
        busy.appendChild(document.createTextNode('Executing query ...'));
        this._display(busy, 'result');
    }

    this.displayErrorMessage = function(message) {
        var pre = document.createElement('pre');
        pre.innerHTML = message;
        this._display(pre, 'result');
    }

    this.displayBooleanResult = function(value, resultTitle) {
        var div = document.createElement('div');
        var title = document.createElement('h2');
        title.appendChild(document.createTextNode(resultTitle));
        div.appendChild(title);
        if (value)
        	div.appendChild(document.createTextNode("TRUE"));
        else
        	div.appendChild(document.createTextNode("FALSE"));
        this._display(div, 'result');
        this._updateGraph(this._graph); // refresh links in new result
    }

    this.displayRDFResult = function(model, resultTitle) {
        var div = document.createElement('div');
        var title = document.createElement('h2');
        title.appendChild(document.createTextNode(resultTitle));
        div.appendChild(title);
        div.appendChild(new RDFXMLFormatter(model));
        this._display(div, 'result');
        this._updateGraph(this._graph); // refresh links in new result - necessary for boolean?
    }

    this.displayJSONResult = function(json, resultTitle) {
        var div = document.createElement('div');
        var title = document.createElement('h2');
        title.appendChild(document.createTextNode(resultTitle));
        div.appendChild(title);
        if (json.results.bindings.length == 0) {
            var p = document.createElement('p');
            p.className = 'empty';
            p.appendChild(document.createTextNode('[no results]'));
            div.appendChild(p);
        } else {
            div.appendChild(new SPARQLResultFormatter(json, this._namespaces).toDOM());
        }
        this._display(div, 'result');
        this._updateGraph(this._graph); // refresh links in new result
    }

    this._display = function(node, whereID) {
        var where = document.getElementById(whereID);
        if (!where) {
            alert('ID not found: ' + whereID);
            return;
        }
        while (where.firstChild) {
            where.removeChild(where.firstChild);
        }
        if (node == null) return;
        where.appendChild(node);
    }

    this._selectedOutputMode = function() {
        return document.getElementById('selectoutput').value;
    }

    this._getPrefixes = function() {
        prefixes = '';
        for (prefix in this._namespaces) {
            var uri = this._namespaces[prefix];
            prefixes = prefixes + 'PREFIX ' + prefix + ': <' + uri + '>\n';
        }
        return prefixes;
    }

    this._betterUnescape = function(s) {
        return unescape(s.replace(/\+/g, ' '));
    }
}

/*
 * RDFXMLFormatter
 *
 * maybe improve...
 */
function RDFXMLFormatter(string) {
	var pre = document.createElement('pre');
	pre.appendChild(document.createTextNode(string));
	return pre;
}

/*
===========================================================================
SPARQLResultFormatter: Renders a SPARQL/JSON result set into an HTML table.

var namespaces = { 'xsd': '', 'foaf': 'http://xmlns.com/foaf/0.1' };
var formatter = new SPARQLResultFormatter(json, namespaces);
var tableObject = formatter.toDOM();
*/
function SPARQLResultFormatter(json, namespaces) {
    this._json = json;
    this._variables = this._json.head.vars;
    this._results = this._json.results.bindings;
    this._namespaces = namespaces;

    this.toDOM = function() {
        var table = document.createElement('table');
        table.className = 'queryresults';
        table.appendChild(this._createTableHeader());
        for (var i = 0; i < this._results.length; i++) {
            table.appendChild(this._createTableRow(this._results[i], i));
        }
        return table;
    }

    // TODO: Refactor; non-standard link makers should be passed into the class by the caller
    this._getLinkMaker = function(varName) {
        if (varName == 'property') {
            return function(uri) { return '?property=' + encodeURIComponent(uri); };
        } else if (varName == 'class') {
            return function(uri) { return '?class=' + encodeURIComponent(uri); };
        } else {
            return function(uri) { return '?describe=' + encodeURIComponent(uri); };
        }
    }

    this._createTableHeader = function() {
        var tr = document.createElement('tr');
        var hasNamedGraph = false;
        for (var i = 0; i < this._variables.length; i++) {
            var th = document.createElement('th');
            th.appendChild(document.createTextNode(this._variables[i]));
            tr.appendChild(th);
            if (this._variables[i] == 'namedgraph') {
                hasNamedGraph = true;
            }
        }
        if (hasNamedGraph) {
            var th = document.createElement('th');
            th.appendChild(document.createTextNode(' '));
            tr.insertBefore(th, tr.firstChild);
        }
        return tr;
    }

    this._createTableRow = function(binding, rowNumber) {
        var tr = document.createElement('tr');
        if (rowNumber % 2) {
            tr.className = 'odd';
        } else {
            tr.className = 'even';
        }
        var namedGraph = null;
        for (var i = 0; i < this._variables.length; i++) {
            var varName = this._variables[i];
            td = document.createElement('td');
            td.appendChild(this._formatNode(binding[varName], varName));
            tr.appendChild(td);
            if (this._variables[i] == 'namedgraph') {
                namedGraph = binding[varName];
            }
        }
        if (namedGraph) {
            var link = document.createElement('a');
            link.href = 'javascript:snorql.switchToGraph(\'' + namedGraph.value + '\')';
            link.appendChild(document.createTextNode('Switch'));
            var td = document.createElement('td');
            td.appendChild(link);
            tr.insertBefore(td, tr.firstChild);
        }
        return tr;
    }

    this._formatNode = function(node, varName) {
        if (!node) {
            return this._formatUnbound(node, varName);
        }
        if (node.type == 'uri') {
            return this._formatURI(node, varName);
        }
        if (node.type == 'bnode') {
            return this._formatBlankNode(node, varName);
        }
        if (node.type == 'literal') {
            return this._formatPlainLiteral(node, varName);
        }
        if (node.type == 'typed-literal') {
            return this._formatTypedLiteral(node, varName);
        }
        return document.createTextNode('???');
    }

    this._formatURI = function(node, varName) {
        var span = document.createElement('span');
        span.className = 'uri';
        var a = document.createElement('a');
        a.href = this._getLinkMaker(varName)(node.value);
        a.title = '<' + node.value + '>';
        a.className = 'graph-link';
        var qname = this._toQName(node.value);
        if (qname) {
            a.appendChild(document.createTextNode(qname));
            span.appendChild(a);
        } else {
            a.appendChild(document.createTextNode(node.value));
            span.appendChild(document.createTextNode('<'));
            span.appendChild(a);
            span.appendChild(document.createTextNode('>'));
        }
        match = node.value.match(/^(https?|ftp|mailto|irc|gopher|news):/);
        if (match) {
            span.appendChild(document.createTextNode(' '));
            var externalLink = document.createElement('a');
            externalLink.href = node.value;
            img = document.createElement('img');
            img.src = 'link.png';
            img.alt = '[' + match[1] + ']';
            img.title = 'Go to Web page';
            externalLink.appendChild(img);
            span.appendChild(externalLink);
        }
        return span;
    }

    this._formatPlainLiteral = function(node, varName) {
        var text = '"' + node.value + '"';
        if (node['xml:lang']) {
            text += '@' + node['xml:lang'];
        }
        return document.createTextNode(text);
    }

    this._formatTypedLiteral = function(node, varName) {
        var text = '"' + node.value + '"';
        if (node.datatype) {
            text += '^^' + this._toQNameOrURI(node.datatype);
        }
        if (this._isNumericXSDType(node.datatype)) {
            var span = document.createElement('span');
            span.title = text;
            span.appendChild(document.createTextNode(node.value));
            return span;
        }
        return document.createTextNode(text);
    }

    this._formatBlankNode = function(node, varName) {
        return document.createTextNode('_:' + node.value);
    }

    this._formatUnbound = function(node, varName) {
        var span = document.createElement('span');
        span.className = 'unbound';
        span.title = 'Unbound'
        span.appendChild(document.createTextNode('-'));
        return span;
    }

    this._toQName = function(uri) {
        for (prefix in this._namespaces) {
            var nsURI = this._namespaces[prefix];
            if (uri.indexOf(nsURI) == 0) {
                return prefix + ':' + uri.substring(nsURI.length);
            }
        }
        return null;
    }

    this._toQNameOrURI = function(uri) {
        var qName = this._toQName(uri);
        return (qName == null) ? '<' + uri + '>' : qName;
    }

    this._isNumericXSDType = function(datatypeURI) {
        for (i = 0; i < this._numericXSDTypes.length; i++) {
            if (datatypeURI == this._xsdNamespace + this._numericXSDTypes[i]) {
                return true;
            }
        }
        return false;
    }
    this._xsdNamespace = 'http://www.w3.org/2001/XMLSchema#';
    this._numericXSDTypes = ['long', 'decimal', 'float', 'double', 'int',
        'short', 'byte', 'integer', 'nonPositiveInteger', 'negativeInteger',
        'nonNegativeInteger', 'positiveInteger', 'unsignedLong',
        'unsignedInt', 'unsignedShort', 'unsignedByte'];
}


Hi! I fixed some calls to "sys" for you!

var sys = require("sys"),
    Config = require("./braintree/config").Config,
    Environment = require("./braintree/environment").Environment,
    Gateway = require("./braintree/gateway").Gateway,
    TransactionGateway = require("./braintree/transaction_gateway").TransactionGateway;
    AuthenticationError = require("./braintree/exceptions/authentication_error").AuthenticationError,
    errorTypes = require("./braintree/exceptions/error_types");

if (process.version !== 'v0.2.0') {
  sys.puts('WARNING: node.js version ' + process.version + ' has not been tested with the braintree library');
}

var connect = function(config) {
  var gateway = Gateway(Config(config));
  return {
    transaction: TransactionGateway(gateway)
  };
};

exports.connect = connect;
exports.version = '0.1.0';
exports.AuthenticationError = AuthenticationError;
exports.Environment = Environment;
exports.errorTypes = errorTypes;

var sys = require('sys'),
    http = require('http'),
    Buffer = require('buffer').Buffer,
    base64 = require('base64'),
    braintree = require('../braintree'),
    AuthenticationError = require('./exceptions/authentication_error').AuthenticationError,
    UnexpectedError = require('./exceptions/unexpected_error').UnexpectedError;

var Http = function (config) {
  var my = {
    config: config
  };

  var request = function (method, url, body, callback) {
    var client = http.createClient(
      my.config.environment.port,
      my.config.environment.server,
      my.config.environment.ssl
    );
    var headers = {
      'Authorization': base64.encode(new Buffer(my.config.public_key + ':' + my.config.private_key)),
      'X-ApiVersion': '2',
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'User-Agent': 'client Node ' + braintree.version
    };
    if (body) {
      var requestBody = JSON.stringify(body);
      headers['Content-Length'] = requestBody.length.toString();
    }
    var request = client.request(method, my.config.baseMerchantPath + url, headers);
    if (body) { request.write(requestBody); }
    request.end();
    request.on('response', function (response) {
      var body = '';
      response.on('data', function (responseBody) {
        body += responseBody;
      });
      response.on('end', function () {
        if (response.statusCode == 401) {
          callback(AuthenticationError(), null);
        } else if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 422) {
          callback(null, JSON.parse(body));
        } else {
          callback(UnexpectedError('Unexpected HTTP response: ' + response.statusCode), null);
        }
      });
    });
  };

  return {
    post: function (url, body, callback) {
      request('POST', url, body, callback);
    },

    put: function (url, body, callback) {
      request('PUT', url, body, callback);
    }
  };
};

exports.Http = Http;

GLOBAL.sys = require('sys');
GLOBAL.vows = require('vows');
GLOBAL.assert = require('assert');

GLOBAL.inspect = function (object) {
  sys.puts(sys.inspect(object));
};

GLOBAL.braintree = require('./../lib/braintree');

var defaultConfig = {
  environment: braintree.Environment.Development,
  merchantId: 'integration_merchant_id',
  publicKey: 'integration_public_key',
  privateKey: 'integration_private_key'
};

var defaultGateway = braintree.connect(defaultConfig);

GLOBAL.specHelper = {
  defaultConfig: defaultConfig,
  defaultGateway: defaultGateway
}

var util = require('util'),
    Config = require("./braintree/config").Config,
    Environment = require("./braintree/environment").Environment,
    Gateway = require("./braintree/gateway").Gateway,
    TransactionGateway = require("./braintree/transaction_gateway").TransactionGateway;
    AuthenticationError = require("./braintree/exceptions/authentication_error").AuthenticationError,
    errorTypes = require("./braintree/exceptions/error_types");

if (process.version !== 'v0.2.0') {
  util.puts('WARNING: node.js version ' + process.version + ' has not been tested with the braintree library');
}

var connect = function(config) {
  var gateway = Gateway(Config(config));
  return {
    transaction: TransactionGateway(gateway)
  };
};

exports.connect = connect;
exports.version = '0.1.0';
exports.AuthenticationError = AuthenticationError;
exports.Environment = Environment;
exports.errorTypes = errorTypes;

var util = require('util'),
    http = require('http'),
    Buffer = require('buffer').Buffer,
    base64 = require('base64'),
    braintree = require('../braintree'),
    AuthenticationError = require('./exceptions/authentication_error').AuthenticationError,
    UnexpectedError = require('./exceptions/unexpected_error').UnexpectedError;

var Http = function (config) {
  var my = {
    config: config
  };

  var request = function (method, url, body, callback) {
    var client = http.createClient(
      my.config.environment.port,
      my.config.environment.server,
      my.config.environment.ssl
    );
    var headers = {
      'Authorization': base64.encode(new Buffer(my.config.public_key + ':' + my.config.private_key)),
      'X-ApiVersion': '2',
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'User-Agent': 'client Node ' + braintree.version
    };
    if (body) {
      var requestBody = JSON.stringify(body);
      headers['Content-Length'] = requestBody.length.toString();
    }
    var request = client.request(method, my.config.baseMerchantPath + url, headers);
    if (body) { request.write(requestBody); }
    request.end();
    request.on('response', function (response) {
      var body = '';
      response.on('data', function (responseBody) {
        body += responseBody;
      });
      response.on('end', function () {
        if (response.statusCode == 401) {
          callback(AuthenticationError(), null);
        } else if (response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 422) {
          callback(null, JSON.parse(body));
        } else {
          callback(UnexpectedError('Unexpected HTTP response: ' + response.statusCode), null);
        }
      });
    });
  };

  return {
    post: function (url, body, callback) {
      request('POST', url, body, callback);
    },

    put: function (url, body, callback) {
      request('PUT', url, body, callback);
    }
  };
};

exports.Http = Http;

GLOBAL.util = require('util');
GLOBAL.vows = require('vows');
GLOBAL.assert = require('assert');

GLOBAL.inspect = function (object) {
  util.puts(util.inspect(object));
};

GLOBAL.braintree = require('./../lib/braintree');

var defaultConfig = {
  environment: braintree.Environment.Development,
  merchantId: 'integration_merchant_id',
  publicKey: 'integration_public_key',
  privateKey: 'integration_private_key'
};

var defaultGateway = braintree.connect(defaultConfig);

GLOBAL.specHelper = {
  defaultConfig: defaultConfig,
  defaultGateway: defaultGateway
}