com.bigdata.rdf.sail.webapp.client.RemoteRepositoryManager.java Source code

Java tutorial

Introduction

Here is the source code for com.bigdata.rdf.sail.webapp.client.RemoteRepositoryManager.java

Source

/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2014.  All rights reserved.
    
Contact:
 SYSTAP, LLC DBA Blazegraph
 2501 Calvert ST NW #106
 Washington, DC 20008
 licenses@blazegraph.com
    
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package com.bigdata.rdf.sail.webapp.client;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.http.entity.ByteArrayEntity;
import org.apache.log4j.Logger;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.query.GraphQueryResult;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.query.impl.MapBindingSet;
import org.openrdf.query.impl.TupleQueryResultImpl;
import org.openrdf.query.resultio.BooleanQueryResultFormat;
import org.openrdf.query.resultio.BooleanQueryResultParser;
import org.openrdf.query.resultio.BooleanQueryResultParserFactory;
import org.openrdf.query.resultio.BooleanQueryResultParserRegistry;
import org.openrdf.query.resultio.TupleQueryResultFormat;
import org.openrdf.query.resultio.TupleQueryResultParser;
import org.openrdf.query.resultio.TupleQueryResultParserFactory;
import org.openrdf.query.resultio.TupleQueryResultParserRegistry;
import org.openrdf.repository.sparql.query.InsertBindingSetCursor;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.RDFParserFactory;
import org.openrdf.rio.RDFParserRegistry;

import com.bigdata.rdf.ServiceProviderHook;
import com.bigdata.rdf.properties.PropertiesFormat;
import com.bigdata.rdf.properties.PropertiesParser;
import com.bigdata.rdf.properties.PropertiesParserFactory;
import com.bigdata.rdf.properties.PropertiesParserRegistry;
import com.bigdata.rdf.properties.PropertiesWriter;
import com.bigdata.rdf.properties.PropertiesWriterRegistry;
import com.bigdata.rdf.sail.model.JsonHelper;
import com.bigdata.rdf.sail.model.RunningQuery;
import com.bigdata.util.InnerCause;
import com.bigdata.util.PropertyUtil;

/**
 * A manager for connections to one or more REST API / SPARQL end points for the
 * same bigdata service.
 * 
 * @author bryan
 */
public class RemoteRepositoryManager extends RemoteRepositoryBase implements AutoCloseable {

    private static final transient Logger log = Logger.getLogger(RemoteRepositoryManager.class);

    final static String EXCEPTION_MSG = "Class not found for service provider hook. "
            + "Blazegraph specific parser extensions will not be available.";

    /**
     * The path to the root of the web application (without the trailing "/").
     * <p>
     * Note: This SHOULD NOT be the SPARQL end point URL. The NanoSparqlServer
     * has a wider interface. This should be the base URL of that interface. The
     * SPARQL end point URL for the default data set is formed by appending
     * <code>/sparql</code>.
     */
    private final String baseServiceURL;

    /**
     * When <code>true</code>, the REST API methods will use the load balancer
     * aware requestURLs. The load balancer has essentially zero cost when not
     * using HA, so it is recommended to always specify <code>true</code>. When
     * <code>false</code>, the REST API methods will NOT use the load balancer
     * aware requestURLs.
     * 
     * @see <a href="http://wiki.blazegraph.com/wiki/index.php/HALoadBalancer">
     *      HALoadBalancer </a>
     */
    protected final boolean useLBS;

    /**
     * The client used for http connections.
     */
    protected final HttpClient httpClient;

    /**
     * IFF an {@link HttpClient} was allocated by the constructor, then this is
     * that reference. When non-<code>null</code> this is always the same
     * reference as {@link #httpClient}.
     */
    private final HttpClient our_httpClient;

    /**
     * Thread pool for processing HTTP responses in background.
     */
    protected final Executor executor;

    /**
     * IFF an {@link Executor} was allocated by the constructor, then this is
     * that reference. When non-<code>null</code> this is always the same
     * reference as {@link #executor}.
     */
    private final ExecutorService our_executor;

    /**
     * The maximum requestURL length before the request is converted into a POST
     * using a <code>application/x-www-form-urlencoded</code> request entity.
     */
    private volatile int maxRequestURLLength;

    /**
     * The HTTP verb that will be used for a QUERY (versus a UPDATE or other
     * mutation operation).
     * 
     * @see #QUERY_METHOD
     */
    private volatile String queryMethod;

    /**
     * Remote client for the transaction manager API.
     */
    private final RemoteTransactionManager transactionManager;

    /**
     * <code>true</code> iff already closed.
     */
    private volatile boolean m_closed = false;

    /**
     * Show Queries Query Parameter
     */
    private static String SHOW_QUERIES = "showQueries";

    /**
     * Return the remote client for the transaction manager API.
     * 
     * @since 1.5.2
     * 
     * @see <a href="http://trac.bigdata.com/ticket/1156"> Support read/write
     *      transactions in the REST API</a>
     */
    public RemoteTransactionManager getTransactionManager() {

        return transactionManager;

    }

    /**
     * The executor for processing http and other client operations.
     */
    public Executor getExecutor() {

        return executor;

    }

    /**
     * The path to the root of the web application (without the trailing "/").
     * <p>
     * Note: This SHOULD NOT be the SPARQL end point URL. The NanoSparqlServer
     * has a wider interface. This should be the base URL of that interface. The
     * SPARQL end point URL for the default data set is formed by appending
     * <code>/sparql</code>.
     */
    public String getBaseServiceURL() {

        return baseServiceURL;

    }

    /**
     * Return <code>true</code> iff the REST API methods will use the load
     * balancer aware requestURLs. The load balancer has essentially zero cost
     * when not using HA, so it is recommended to always specify
     * <code>true</code>. When <code>false</code>, the REST API methods will NOT
     * use the load balancer aware requestURLs.
     */
    public boolean getUseLBS() {

        return useLBS;

    }

    /**
     * Return the maximum requestURL length before the request is converted into
     * a POST using a <code>application/x-www-form-urlencoded</code> request
     * entity.
     * 
     * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/619">
     *      RemoteRepository class should use application/x-www-form-urlencoded
     *      for large POST requests </a>
     */
    public int getMaxRequestURLLength() {

        return maxRequestURLLength;

    }

    public void setMaxRequestURLLength(final int newVal) {

        if (newVal <= 0)
            throw new IllegalArgumentException();

        this.maxRequestURLLength = newVal;

    }

    /**
     * Return the HTTP verb that will be used for a QUERY (versus an UPDATE or
     * other mutation operations) (default {@value #DEFAULT_QUERY_METHOD}). POST
     * can often handle larger queries than GET due to limits at the HTTP client
     * layer and will defeat http caching and thus provide a current view of the
     * committed state of the SPARQL end point when the end point is a
     * read/write database. However, GET supports HTTP caching and can scale
     * much better when the SPARQL end point is a read-only resource or a
     * read-mostly resource where stale reads are acceptable.
     * 
     * @see #setQueryMethod(String)
     */
    public String getQueryMethod() {

        return queryMethod;

    }

    /**
     * Set the default HTTP verb for QUERY and other idempotant operations.
     * 
     * @param method
     *            The method which may be "POST" or "GET".
     * 
     * @see #getQueryMethod()
     */
    public void setQueryMethod(final String method) {

        if ("POST".equalsIgnoreCase(method) || "GET".equalsIgnoreCase(method)) {

            this.queryMethod = method.toUpperCase();

        } else {

            throw new IllegalArgumentException();

        }

    }

    /**
     * Create a manager that is not aware of a specific blazegraph backend. This
     * constructor is intended for patterns where a sparql end point is
     * available but the top-level serviceURL for blazegraph is either not
     * visible or not known:
     * 
     * <pre>
     * new RemoteRepositoryManager().getRepositoryForURL(sparqlEndpointURL)
     * </pre>
     * 
     * The same pattern MAY be used to perform SPARQL QUERY or SPARQL UPDATE
     * operations against non-blazegraph sparql end points.
     */
    public RemoteRepositoryManager() {

        this("http://localhost/no-service-URL");

    }

    /**
     * Create a manager client for the specified serviceURL. The serviceURL has
     * the typical form
     * 
     * <pre>
     * http://host:port/bigdata
     * </pre>
     * 
     * The serviceURL can be used to obtain sparql end point URLs for:
     * <dl>
     * <dt>The default namespace</dt>
     * <dd>http://host:port/bigdata/sparql</dd>
     * <dt>The XYZ namespace</dt>
     * <dd>http://host:port/bigdata/namespace/XYZ/sparql</dd>
     * </dl>
     * 
     * The serviceURL can also be used to access the multi-tenancy API and the
     * transaction management API. See the wiki for more details.
     * 
     * @param serviceURL
     *            The path to the root of the web application (without the
     *            trailing "/"). <code>/sparql</code> will be appended to this
     *            path to obtain the SPARQL end point for the default data set.
     */
    public RemoteRepositoryManager(final String serviceURL) {

        /*
         * TODO Why is useLBS:=false? Is there a problem when it is true and we
         * are not actually using an HA deployment? E.g., single server
         * deployment under a non-jetty servlet container where the
         * LoadBalancerServlet is not deployed?
         */
        this(serviceURL, false/* useLBS */);

    }

    /**
     * Create a remote client for the specified serviceURL that optionally use
     * the load balanced URLs.
     * 
     * @param serviceURL
     *            The path to the root of the web application (without the
     *            trailing "/"). <code>/sparql</code> will be appended to this
     *            path to obtain the SPARQL end point for the default data set.
     * @param useLBS
     *            When <code>true</code>, the REST API methods will use the load
     *            balancer aware requestURLs. The load balancer has essentially
     *            zero cost when not using HA, so it is recommended to always
     *            specify <code>true</code>. When <code>false</code>, the REST
     *            API methods will NOT use the load balancer aware requestURLs.
     */
    public RemoteRepositoryManager(final String serviceURL, final boolean useLBS) {

        this(serviceURL, useLBS, null/* httpClient */, null/* executor */);

    }

    /**
     * Create a remote client for the specified serviceURL.
     * 
     * @param serviceURL
     *            The path to the root of the web application (without the
     *            trailing "/"). <code>/sparql</code> will be appended to this
     *            path to obtain the SPARQL end point for the default data set.
     * @param httpClient
     *            The client is managed externally and, in particular, 
     *            may be shared, so we don't close it in {@link #close()}.
     *            When not present (null), an {@link HttpClient} will be 
     *            allocated and scoped to this
     *            {@link RemoteRepositoryManager} instance, and closed
     *            in {@link #close()}.
     * @param executor
     *            An executor used to service http client requests. (optional).
     *            When not present, an {@link Executor} will be allocated and
     *            scoped to this {@link RemoteRepositoryManager} instance.
     * 
     *            TODO Should this be deprecated since it does not force the
     *            caller to choose a value for <code>useLBS</code>?
     *            <p>
     *            This version does not force the caller to decide whether or
     *            not the LBS pattern will be used. In general, it should be
     *            used if the end point is bigdata. This class is generally, but
     *            not always, used with a bigdata end point. The main exception
     *            is SPARQL Basic Federated Query. For that use case we can not
     *            assume that the end point is bigdata and thus we can not use
     *            the LBS prefix.
     */
    public RemoteRepositoryManager(final String serviceURL, final HttpClient httpClient, final Executor executor) {

        this(serviceURL, false/* useLBS */, httpClient, executor);

    }

    /**
     * Create a remote client for the specified serviceURL (core impl).
     * 
     * @param serviceURL
     *            The path to the root of the web application (without the
     *            trailing "/"). <code>/sparql</code> will be appended to this
     *            path to obtain the SPARQL end point for the default data set.
     * @param useLBS
     *            When <code>true</code>, the REST API methods will use the load
     *            balancer aware requestURLs. The load balancer has essentially
     *            zero cost when not using HA, so it is recommended to always
     *            specify <code>true</code>. When <code>false</code>, the REST
     *            API methods will NOT use the load balancer aware requestURLs.
     * @param httpClient
     *            The client is managed externally and, in particular, 
     *            may be shared, so we don't close it in {@link #close()}.
     *            When not present (null), an {@link HttpClient} will be 
     *            allocated and scoped to this
     *            {@link RemoteRepositoryManager} instance, and closed
     *            in {@link #close()}.
     * @param executor
     *            An executor used to service http client requests. (optional).
     *            When not present, an {@link Executor} will be allocated and
     *            scoped to this {@link RemoteRepositoryManager} instance.
     */
    public RemoteRepositoryManager(final String serviceURL, final boolean useLBS, final HttpClient httpClient,
            final Executor executor) {

        if (serviceURL == null)
            throw new IllegalArgumentException();

        this.baseServiceURL = serviceURL;

        this.useLBS = useLBS;

        if (httpClient == null) {

            /*
             * Allocate the HttpClient. It will be closed when this class is
             * closed.
             */
            this.httpClient = our_httpClient = HttpClientConfigurator.getInstance().newInstance();

        } else {

            /*
             * Note: Client *might* be AutoCloseable, in which case we will
             * close it.
             */
            this.httpClient = httpClient;
            this.our_httpClient = null;

        }

        if (executor == null) {

            /*
             * Allocate the executor. It will be shutdown when this class is
             * closed.
             * 
             * See #1191 (remote connection uses non-daemon thread pool).
             */
            this.executor = our_executor = Executors
                    .newCachedThreadPool(DaemonThreadFactory.defaultThreadFactory());

        } else {

            // We are using the caller's executor. We will not shut it down.
            this.executor = executor;
            this.our_executor = null;

        }

        assertHttpClientRunning();

        this.transactionManager = new RemoteTransactionManager(this);

        setMaxRequestURLLength(Integer.parseInt(
                System.getProperty(MAX_REQUEST_URL_LENGTH, Integer.toString(DEFAULT_MAX_REQUEST_URL_LENGTH))));

        setQueryMethod(System.getProperty(QUERY_METHOD, DEFAULT_QUERY_METHOD));

        // See #1235 bigdata-client does not invoke
        // ServiceProviderHook.forceLoad()
        try {
            ServiceProviderHook.forceLoad();
        } catch (java.lang.NoClassDefFoundError | java.lang.ExceptionInInitializerError e) {
            // If we are running in unit tests in the client package this
            // introduces
            // a runtime dependency on the bigdata-core artifact. Just catch the
            // Exception and let the unit test complete.
            if (log.isInfoEnabled()) {
                log.info(EXCEPTION_MSG);
            }
        }

    }

    /**
     * {@inheritDoc}
     * <p>
     * Ensure resource is closed.
     * 
     * @see AutoCloseable
     * @see <a href="http://trac.bigdata.com/ticket/1207" > Memory leak in
     *      CI? </a>
     */
    @Override
    protected void finalize() throws Throwable {

        close();

        super.finalize();

    }

    @Override
    public void close() throws Exception {

        if (m_closed) {
            // Already closed.
            return;
        }

        if (our_httpClient != null) {

            /*
             * This HttpClient was allocated by our constructor. We will shut it
             * down now (unless it is already stopping or stopped).
             */

            if (our_httpClient instanceof AutoCloseable) {
                // The instance is managed by AutoCloseHttpClient or a similar 
                // class, so close() instead of just stop():
                ((AutoCloseable) our_httpClient).close();
            } else {
                // Not an AutoCloseable, but we should still try to stop it:

                if (!our_httpClient.isStopping() && !our_httpClient.isStopped()) {

                    our_httpClient.stop();

                }
            }

        }

        // Note that we do not close httpClient here, unless it is 
        // our_httpClient, because it came as 
        // a parameter to a constructor, may be shared by multiple 
        // RemoteRepositoryManager instances and other objects, and 
        // thus has to be closed elswhere (e.g., QueryEngine.shutdown()).

        if (our_executor != null) {

            /*
             * This thread pool was allocated by our constructor. Shut it down
             * now.
             */
            our_executor.shutdownNow();

        }

        m_closed = true;

    }

    @Override
    public String toString() {

        return super.toString() + "{baseServiceURL=" + baseServiceURL + ", useLBS=" + useLBS + "}";

    }

    /**
     * Return the base URL for a remote repository (less the /sparql path
     * component).
     * 
     * @param namespace
     *            The namespace.
     * 
     * @return The base URL.
     * 
     * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/689" >
     *      Missing URL encoding in RemoteRepositoryManager </a>
     */
    protected String getRepositoryBaseURLForNamespace(final String namespace) {

        return baseServiceURL + "/namespace/" + ConnectOptions.urlEncode(namespace);
    }

    /**
     * Returns the SPARQL endpoint URL for the given namespace or the default SPARQL
     * endpoint in case namespace is null.
     * 
     * @param namespace
     * @return the namespace 
     */
    private String getSparqlEndpointUrlForNamespaceOrDefault(final String namespace) {

        return namespace == null ? baseServiceURL + "/sparql"
                : getRepositoryBaseURLForNamespace(namespace) + "/sparql";
    }

    /**
     * Obtain a flyweight {@link RemoteRepository} for the default namespace
     * associated with the remote service.
     */
    public RemoteRepository getRepositoryForDefaultNamespace() {

        return getRepositoryForURL(baseServiceURL + "/sparql");

    }

    /**
     * Obtain a flyweight {@link RemoteRepository} for a data set managed by the
     * remote service.
     * 
     * @param namespace
     *            The name of the data set (its bigdata namespace).
     * 
     * @return An interface which may be used to talk to that data set.
     */
    public RemoteRepository getRepositoryForNamespace(final String namespace) {

        return getRepositoryForURL(getRepositoryBaseURLForNamespace(namespace) + "/sparql");

    }

    // /**
    // * Obtain a flyweight {@link RemoteRepository} for the data set having the
    // specified
    // * SPARQL end point.
    // *
    // * @param sparqlEndpointURL
    // * The URL of the SPARQL end point.
    // * @param useLBS
    // * When <code>true</code>, the REST API methods will use the load
    // * balancer aware requestURLs. The load balancer has essentially
    // * zero cost when not using HA, so it is recommended to always
    // * specify <code>true</code>. When <code>false</code>, the REST
    // * API methods will NOT use the load balancer aware requestURLs.
    // *
    // * @return An interface which may be used to talk to that data set.
    // */
    // @Deprecated // The useLBS property is on the RemoteRepositoryManager and
    // is ignored by this method.
    // public RemoteRepository getRepositoryForURL(final String
    // sparqlEndpointURL,
    // final boolean useLBS) {
    //
    // return new RemoteRepository(this, sparqlEndpointURL);
    //
    // }

    /**
     * Obtain a flyweight {@link RemoteRepository} for the data set having the
     * specified SPARQL end point. The load balancer will be used or not as per
     * the parameters to the {@link RemoteRepositoryManager} constructor.
     * 
     * @param sparqlEndpointURL
     *            The URL of the SPARQL end point.
     * 
     * @return An interface which may be used to talk to that data set.
     */
    public RemoteRepository getRepositoryForURL(final String sparqlEndpointURL) {

        return getRepositoryForURL(sparqlEndpointURL, null/* timestamp */);

    }

    /**
     * Obtain a flyweight {@link RemoteRepository} for the data set having the
     * specified SPARQL end point. The load balancer will be used or not as per
     * the parameters to the {@link RemoteRepositoryManager} constructor.
     * 
     * @param sparqlEndpointURL
     *            The URL of the SPARQL end point.
     * @param timestamp
     *            The timestamp that will be added to all requests for the
     *            sparqlEndPoint (optional).
     * 
     * @return An interface which may be used to talk to that data set.
     */
    public RemoteRepository getRepositoryForURL(final String sparqlEndpointURL, final IRemoteTx tx) {

        return new RemoteRepository(this, sparqlEndpointURL, tx);

    }

    /**
     * Obtain a <a href="http://vocab.deri.ie/void/"> VoID </a> description of
     * the configured KBs. Each KB has its own namespace and corresponds to a
     * VoID "data set".
     * <p>
     * Note: This method uses an HTTP GET and hence can be cached by the server.
     * 
     * @return A <a href="http://vocab.deri.ie/void/"> VoID </a> description of
     *         the configured KBs.
     * 
     * @throws Exception
     */
    public GraphQueryResult getRepositoryDescriptions() throws Exception {

        return getRepositoryDescriptions(UUID.randomUUID());

    }

    /**
     * Obtain a <a href="http://vocab.deri.ie/void/"> VoID </a> description of
     * the configured KBs. Each KB has its own namespace and corresponds to a
     * VoID "data set".
     * <p>
     * Note: This method uses an HTTP GET and hence can be cached by the server.
     * 
     * @param uuid
     *            The {@link UUID} to be associated with this request.
     * 
     * @return A <a href="http://vocab.deri.ie/void/"> VoID </a> description of
     *         the configured KBs.
     * 
     * @throws Exception
     */
    public GraphQueryResult getRepositoryDescriptions(final UUID uuid) throws Exception {

        final ConnectOptions opts = newConnectOptions(baseServiceURL + "/namespace", uuid, null/* tx */);

        opts.method = "GET";

        opts.setAcceptHeader(ConnectOptions.DEFAULT_GRAPH_ACCEPT_HEADER);

        // Note: asynchronous result set processing.
        return graphResults(opts, uuid, null /* listener */);

    }

    /**
     * Create a new KB instance.
     * 
     * @param namespace
     *            The namespace of the KB instance.
     * @param properties
     *            The configuration properties for that KB instance.
     * 
     * @throws Exception
     * 
     * @see <a href="http://trac.bigdata.com/ticket/1257"> createRepository()
     *      does not set the namespace on the Properties</a>
     */
    public void createRepository(final String namespace, final Properties properties) throws Exception {

        createRepository(namespace, properties, UUID.randomUUID());

    }

    /**
     * 
     * Create a new KB instance.
     * 
     * @param namespace
     *            The namespace of the KB instance.
     * @param properties
     *            The configuration properties for that KB instance.
     * @param uuid
     *            The {@link UUID} to be associated with this request.
     * 
     * @throws Exception
     * 
     * @see <a href="http://trac.bigdata.com/ticket/1257"> createRepository()
     *      does not set the namespace on the Properties</a>
     */
    public void createRepository(final String namespace, final Properties properties, final UUID uuid)
            throws Exception {

        if (namespace == null)
            throw new IllegalArgumentException();
        if (properties == null)
            throw new IllegalArgumentException();
        if (uuid == null)
            throw new IllegalArgumentException();
        // if (properties.getProperty(OPTION_CREATE_KB_NAMESPACE) == null)
        // throw new IllegalArgumentException("Property not defined: "
        // + OPTION_CREATE_KB_NAMESPACE);

        // Set the namespace property.
        final Properties tmp = PropertyUtil.flatCopy(properties);
        tmp.setProperty(OPTION_CREATE_KB_NAMESPACE, namespace);

        /*
         * Note: This operation does not currently permit embedding into a
         * read/write tx.
         */
        final ConnectOptions opts = newConnectOptions(baseServiceURL + "/namespace", uuid, null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            final PropertiesFormat format = PropertiesFormat.XML;

            final ByteArrayOutputStream baos = new ByteArrayOutputStream();

            final PropertiesWriter writer = PropertiesWriterRegistry.getInstance().get(format).getWriter(baos);

            writer.write(tmp);

            final byte[] data = baos.toByteArray();

            final ByteArrayEntity entity = new ByteArrayEntity(data);

            entity.setContentType(format.getDefaultMIMEType());

            opts.entity = entity;

        }

        try {

            checkResponseCode(response = doConnect(opts));
        } finally {
            if (response != null)
                response.abort();

        }

    }

    /**
     * 
     * Prepare configuration properties for a new KB instance.
     * 
     * @param namespace
     *            The namespace of the KB instance.
     * @param properties
     *            The configuration properties for that KB instance.
     * 
     * @return The effective configuration properties for that named data set.
     * 
     * @throws Exception
     */
    public Properties getPreparedProperties(final String namespace, final Properties properties) throws Exception {
        return getPreparedProperties(namespace, properties, UUID.randomUUID());
    }

    public Properties getPreparedProperties(final String namespace, final Properties properties, final UUID uuid)
            throws Exception {

        if (namespace == null)
            throw new IllegalArgumentException();
        if (properties == null)
            throw new IllegalArgumentException();
        if (uuid == null)
            throw new IllegalArgumentException();

        // Set the namespace property.
        final Properties tmp = PropertyUtil.flatCopy(properties);
        tmp.setProperty(OPTION_CREATE_KB_NAMESPACE, namespace);

        final String sparqlEndpointURL = baseServiceURL + "/namespace/prepareProperties";

        /*
         * Note: This operation does not currently permit embedding into a
         * read/write tx.
         */
        final ConnectOptions opts = newConnectOptions(baseServiceURL + "/namespace/prepareProperties", uuid,
                null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            final PropertiesFormat format = PropertiesFormat.XML;

            final ByteArrayOutputStream baos = new ByteArrayOutputStream();

            final PropertiesWriter writer = PropertiesWriterRegistry.getInstance().get(format).getWriter(baos);

            writer.write(tmp);

            final byte[] data = baos.toByteArray();

            final ByteArrayEntity entity = new ByteArrayEntity(data);

            entity.setContentType(format.getDefaultMIMEType());

            opts.entity = entity;

        }

        boolean consumeNeeded = true;
        try {

            checkResponseCode(response = doConnect(opts));

            final String contentType = response.getContentType();

            if (contentType == null)
                throw new RuntimeException("Not found: Content-Type");

            final MiniMime mimeType = new MiniMime(contentType);

            final PropertiesFormat format = PropertiesFormat.forMIMEType(mimeType.getMimeType());

            if (format == null)
                throw new IOException(
                        "Could not identify format for service response: serviceURI=" + sparqlEndpointURL
                                + ", contentType=" + contentType + " : response=" + response.getResponseBody());

            final PropertiesParserFactory factory = PropertiesParserRegistry.getInstance().get(format);

            if (factory == null)
                throw new RuntimeException(
                        "ParserFactory not found: Content-Type=" + contentType + ", format=" + format);

            final PropertiesParser parser = factory.getParser();

            final Properties preparedProperties = parser.parse(response.getInputStream());

            consumeNeeded = false;

            return preparedProperties;

        } catch (Exception e) {
            consumeNeeded = !InnerCause.isInnerCause(e, HttpException.class);
            throw e;

        } finally {
            if (response != null)
                response.abort();

        }

    }

    /**
     * 
     * 
     * @param 
     * @param 
     * 
     * @return 
     * 
     * @throws Exception
     */
    public void rebuildTextIndex(final String namespace, final boolean forceBuildTextIndex) throws Exception {
        rebuildTextIndex(namespace, forceBuildTextIndex, UUID.randomUUID());
    }

    public void rebuildTextIndex(final String namespace, final boolean forceBuildTextIndex, final UUID uuid)
            throws Exception {

        if (namespace == null)
            throw new IllegalArgumentException();
        if (uuid == null)
            throw new IllegalArgumentException();

        final String endpointURL = baseServiceURL + "/namespace/" + namespace + "/textIndex";

        /*
         * Note: This operation does not currently permit embedding into a
         * read/write tx.
         */
        final ConnectOptions opts = newConnectOptions(endpointURL, uuid, null/* tx */);

        if (forceBuildTextIndex) {

            opts.addRequestParam(RemoteRepositoryDecls.FORCE_INDEX_CREATE, "true");

        }

        JettyResponseListener response = null;

        boolean consumeNeeded = true;

        try {

            checkResponseCode(response = doConnect(opts));

            consumeNeeded = false;

        } catch (Exception e) {
            consumeNeeded = !InnerCause.isInnerCause(e, HttpException.class);
            throw e;

        } finally {

            if (response != null)
                response.abort();

        }

    }

    /**
     * Destroy a KB instance.
     * 
     * @param namespace
     *            The namespace of the KB instance.
     * 
     * @throws Exception
     */
    public void deleteRepository(final String namespace) throws Exception {

        deleteRepository(namespace, UUID.randomUUID());

    }

    /**
     * Destroy a KB instance.
     * 
     * @param namespace
     *            The namespace of the KB instance.
     * @param uuid
     *            The {@link UUID} to be assigned to the request.a
     * 
     * @throws Exception
     */
    public void deleteRepository(final String namespace, final UUID uuid) throws Exception {

        final ConnectOptions opts = newConnectOptions(getRepositoryBaseURLForNamespace(namespace), uuid,
                null/* txId */);

        opts.method = "DELETE";

        JettyResponseListener response = null;

        try {

            checkResponseCode(response = doConnect(opts));

        } finally {

            if (response != null)
                response.abort();

        }

    }

    /**********************************************************************
     ************************** Mapgraph Servlet **************************
     **********************************************************************/
    static private final String COMPUTE_MODE = "computeMode";
    static private final String MAPGRAPH = "mapgraph";
    static private final String MAPGRAPH_RESET = "reset";
    static private final String MAPGRAPH_PUBLISH = "publish";
    static private final String MAPGRAPH_DROP = "drop";
    static private final String MAPGRAPH_CHECK_RUNTIME_AVAILABLE = "runtimeAvailable";
    static private final String CHECK_PUBLISHED = "checkPublished";

    public enum ComputeMode {
        CPU, GPU;
    }

    /**
     * Publishes the given namespace to the mapgraph runtime. If
     * the namespace if already published, no action is performed.
     * The return value is false in the latter case, true otherwise.
     * If the namespace that is passed in is null, the default
     * namespace will be used.
     * 
     * @return true if the namespace was not yet published already, 
     *         false otherwise (i.e., in case no action has been taken)
     * @throws NoGPUAccelerationAvailableException
     */
    public boolean publishNamespaceToMapgraph(final String namespace) throws Exception {

        assertMapgraphRuntimeAvailable();

        if (namespacePublishedToMapgraph(namespace))
            return false; // nothing to be done

        final String repositoryUrl = getSparqlEndpointUrlForNamespaceOrDefault(namespace);

        final ConnectOptions opts = newConnectOptions(repositoryUrl, UUID.randomUUID(), null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            opts.addRequestParam(MAPGRAPH, MAPGRAPH_PUBLISH);
            opts.method = "POST";
        }

        try {

            checkResponseCode(response = doConnect(opts));

        } finally {

            if (response != null)
                response.abort();

        }

        return true;

    }

    /**
     * Drops the given namespace from the mapgraph runtime. If
     * the namespace was not registered in the runtime, no action is
     * performed. The return value is false in the latter case, true otherwise.
     * If the namespace that is passed in is null, the default
     * namespace will be used.
     * 
     * @return true if the namespace was published before and has been dropped, 
     *         false otherwise (i.e., in case no action has been taken)
     * @throws NoGPUAccelerationAvailableException
     */
    public boolean dropNamespaceFromMapgraph(final String namespace) throws Exception {

        assertMapgraphRuntimeAvailable();

        if (!namespacePublishedToMapgraph(namespace))
            return false; // nothing to be done

        final String repositoryUrl = getSparqlEndpointUrlForNamespaceOrDefault(namespace);

        final ConnectOptions opts = newConnectOptions(repositoryUrl, UUID.randomUUID(), null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            opts.addRequestParam(MAPGRAPH, MAPGRAPH_DROP);
            opts.method = "POST";
        }

        try {

            checkResponseCode(response = doConnect(opts));

        } finally {

            if (response != null)
                response.abort();

        }

        return true;
    }

    /**
     * Checks whether the given namespace has been published. If null is passed
     * in, the method performs a check for the default namespace.
     * 
     * @return true if the namespace is registered for acceleration,
     *         false otherwise
     * @throws NoGPUAccelerationAvailableException
     */
    public boolean namespacePublishedToMapgraph(final String namespace) throws Exception {

        assertMapgraphRuntimeAvailable();

        final String repositoryUrl = getSparqlEndpointUrlForNamespaceOrDefault(namespace);

        final ConnectOptions opts = newConnectOptions(repositoryUrl, UUID.randomUUID(), null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            opts.setAcceptHeader("Accept: text/plain");
            opts.addRequestParam(MAPGRAPH, CHECK_PUBLISHED);
            opts.method = "POST";
        }

        try {

            checkResponseCode(response = doConnect(opts));

            final String responseBody = response.getResponseBody();

            return responseBody != null && responseBody.contains("true");

        } finally {

            if (response != null)
                response.abort();

        }

    }

    /**
     * Resets the mapgraph runtime for the compute mode.
     * 
     * @param computeMode the desired compute mode
     */
    public void resetMapgraphRuntime(final ComputeMode computeMode) throws Exception {

        assertMapgraphRuntimeAvailable();

        if (computeMode == null) {
            throw new IllegalArgumentException("Compute mode must not be null");
        }

        final String repositoryUrl = getSparqlEndpointUrlForNamespaceOrDefault(null /* default namespace */);

        final ConnectOptions opts = newConnectOptions(repositoryUrl, UUID.randomUUID(), null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            opts.addRequestParam(MAPGRAPH, MAPGRAPH_RESET);
            opts.addRequestParam(COMPUTE_MODE, computeMode.toString());
            opts.method = "POST";
        }

        try {

            checkResponseCode(response = doConnect(opts));

        } finally {

            if (response != null)
                response.abort();

        }

    }

    /**
     * Returns the current status report for mapgraph.
     * 
     * @return the status report as human-readable string
     * @throws Exception
     */
    public String getMapgraphStatus() throws Exception {

        String repositoryUrl = baseServiceURL + "/status";

        /**
         * First reset the runtime
         */
        final ConnectOptions opts = newConnectOptions(repositoryUrl, UUID.randomUUID(), null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            opts.addRequestParam(MAPGRAPH, "");
            opts.method = "GET";
        }

        try {

            response = doConnect(opts);
            return response.getResponseBody();

        } finally {

            if (response != null)
                response.abort();

        }

    }

    /**
     * Checks whether the mapgraph runtime is available.
     * @return
     */
    public boolean mapgraphRuntimeAvailable() throws Exception {

        final String repositoryUrl = getSparqlEndpointUrlForNamespaceOrDefault(null /* default namespace */);

        /**
         * First reset the runtime
         */
        final ConnectOptions opts = newConnectOptions(repositoryUrl, UUID.randomUUID(), null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            opts.addRequestParam(MAPGRAPH, MAPGRAPH_CHECK_RUNTIME_AVAILABLE);
            opts.method = "POST";
        }

        try {

            response = doConnect(opts);

            return response.getStatus() == 200 /* HTTP OK */;

        } finally {

            if (response != null)
                response.abort();

        }

    }

    void assertMapgraphRuntimeAvailable() throws Exception {
        if (!mapgraphRuntimeAvailable())
            throw new NoGPUAccelerationAvailable();
    }

    /**
     * Return the effective configuration properties for the named data set.
     * <p>
     * Note: While it is possible to change some configuration options are a
     * data set has been created, many aspects of a "data set" configuration are
     * "baked in" when the data set is created and can not be changed. For this
     * reason, no general purpose mechanism is being offered to change the
     * properties for a configured data set instance.
     * 
     * @param namespace
     *            The name of the data set.
     * 
     * @return The effective configuration properties for that named data set.
     * 
     * @throws Exception
     */
    public Properties getRepositoryProperties(final String namespace) throws Exception {

        return getRepositoryProperties(namespace, UUID.randomUUID());

    }

    public Properties getRepositoryProperties(final String namespace, final UUID uuid) throws Exception {

        final String sparqlEndpointURL = getRepositoryBaseURLForNamespace(namespace);

        final ConnectOptions opts = newConnectOptions(sparqlEndpointURL + "/properties", uuid/* queryId */,
                null/* txId */);

        opts.method = "GET";

        JettyResponseListener response = null;

        opts.setAcceptHeader(ConnectOptions.MIME_PROPERTIES_XML);
        boolean consumeNeeded = true;
        try {

            checkResponseCode(response = doConnect(opts));

            final String contentType = response.getContentType();

            if (contentType == null)
                throw new RuntimeException("Not found: Content-Type");

            final MiniMime mimeType = new MiniMime(contentType);

            final PropertiesFormat format = PropertiesFormat.forMIMEType(mimeType.getMimeType());

            if (format == null)
                throw new IOException(
                        "Could not identify format for service response: serviceURI=" + sparqlEndpointURL
                                + ", contentType=" + contentType + " : response=" + response.getResponseBody());

            final PropertiesParserFactory factory = PropertiesParserRegistry.getInstance().get(format);

            if (factory == null)
                throw new RuntimeException(
                        "ParserFactory not found: Content-Type=" + contentType + ", format=" + format);

            final PropertiesParser parser = factory.getParser();

            final Properties properties = parser.parse(response.getInputStream());

            consumeNeeded = false;

            return properties;
        } catch (Exception e) {
            consumeNeeded = !InnerCause.isInnerCause(e, HttpException.class);
            throw e;
        } finally {

            if (response != null && consumeNeeded)
                response.abort();

        }

    }

    /**
     * Initiate an online backup using the {@link com.bigdata.rdf.sail.webapp.BackupServlet}.
     * 
     * 
     * @param file  -- The name of the file for the backup. (default = "backup.jnl")
     * @param compress -- Use compression for the snapshot (default = false)
     * @param block  -- Block on the response (default = true)
     * 
     * @see https://jira.blazegraph.com/browse/BLZG-1727
     */
    public void onlineBackup(final String file, final boolean compress, final boolean block) throws Exception {

        /**
         * Use copies of these from {@link com.bigdata.rdf.sail.webapp.BackupServlet}
         * to avoid introducing cyclical dependency with bigdata-core.
         * 
         */

        final String COMPRESS = "compress";

        final String FILE = "file";

        final String BLOCK = "block";

        final ConnectOptions opts = newConnectOptions(baseServiceURL + "/backup", UUID.randomUUID(), null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            opts.addRequestParam(FILE, file);
            opts.addRequestParam(COMPRESS, Boolean.toString(compress));
            opts.addRequestParam(BLOCK, Boolean.toString(block));

            opts.method = "POST";
        }

        try {

            checkResponseCode(response = doConnect(opts));
        } finally {
            if (response != null)
                response.abort();

        }

    }

    /**
     * 
     * Initiate the data loader for a namespace within the a NSS
     * 
     * @param properties
     *            The properties for the DataLoader Servlet
     * 
     * @throws Exception
     * 
     * @see BLZG-1713
     */
    public void doDataLoader(final Properties properties) throws Exception {

        if (properties == null)
            throw new IllegalArgumentException();

        final Properties tmp = PropertyUtil.flatCopy(properties);

        /*
         * Note: This operation does not currently permit embedding into a
         * read/write tx.
         */
        final ConnectOptions opts = newConnectOptions(baseServiceURL + "/dataloader", UUID.randomUUID(),
                null/* tx */);

        JettyResponseListener response = null;

        // Setup the request entity.
        {

            final PropertiesFormat format = PropertiesFormat.XML;

            final ByteArrayOutputStream baos = new ByteArrayOutputStream();

            final PropertiesWriter writer = PropertiesWriterRegistry.getInstance().get(format).getWriter(baos);

            writer.write(tmp);

            final byte[] data = baos.toByteArray();

            final ByteArrayEntity entity = new ByteArrayEntity(data);

            entity.setContentType(format.getDefaultMIMEType());

            opts.entity = entity;

            opts.method = "POST";

        }

        try {

            checkResponseCode(response = doConnect(opts));
        } finally {
            if (response != null)
                response.abort();

        }

    }

    /**
     * Connect to a SPARQL end point (GET or POST query only).
     * 
     * @param opts
     *            The connection options.
     * 
     * @return The connection.
     * 
     * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/619">
     *      RemoteRepository class should use application/x-www-form-urlencoded
     *      for large POST requests </a>
     */
    public JettyResponseListener doConnect(final ConnectOptions opts) throws Exception {

        assertHttpClientRunning();

        /*
         * Generate the fully formed and encoded URL.
         */

        // The requestURL (w/o URL query parameters).
        final String requestURL = opts.getRequestURL(getContextPath(), getUseLBS());

        final StringBuilder urlString = new StringBuilder(requestURL);

        /*
         * FIXME (***) Why are we using one approach to add the parameters here
         * and then a different approach if we do a POST? Either one or the
         * other I think. Try moving this into an else {} block below (if not a
         * POST, then add query parameters).
         */
        ConnectOptions.addQueryParams(urlString, opts.requestParams);

        final boolean isLongRequestURL = urlString.length() > getMaxRequestURLLength();

        if (isLongRequestURL && opts.method.equals("POST") && opts.entity == null) {

            /*
             * URL is too long. Reset the URL to just the service endpoint and
             * use application/x-www-form-urlencoded entity instead. Only in
             * cases where there is not already a request entity (SPARQL query
             * and SPARQL update).
             */

            urlString.setLength(0);
            urlString.append(requestURL);

            opts.entity = ConnectOptions.getFormEntity(opts.requestParams);

        } else if (isLongRequestURL && opts.method.equals("GET") && opts.entity == null) {

            /*
             * Convert automatically to a POST if the request URL is too long.
             * 
             * Note: [opts.entity == null] should always be true for a GET so
             * this bit is a paranoia check.
             */

            opts.method = "POST";

            urlString.setLength(0);
            urlString.append(requestURL);

            opts.entity = ConnectOptions.getFormEntity(opts.requestParams);

        }

        if (log.isDebugEnabled()) {
            log.debug("*** Request ***");
            log.debug(requestURL);
            log.debug(opts.method);
            log.debug("query=" + opts.getRequestParam("query"));
            log.debug(urlString.toString());
        }

        Request request = null;
        try {

            request = (HttpRequest) newRequest(urlString.toString(), opts.method);

            if (opts.requestHeaders != null) {

                for (Map.Entry<String, String> e : opts.requestHeaders.entrySet()) {

                    request.header(e.getKey(), e.getValue());

                    if (log.isDebugEnabled())
                        log.debug(e.getKey() + ": " + e.getValue());

                }

            }

            if (opts.entity != null) {

                final EntityContentProvider cp = new EntityContentProvider(opts.entity);

                request.content(cp, cp.getContentType());

            }

            final long queryTimeoutMillis;
            {
                final String s = opts.getHeader(HTTP_HEADER_BIGDATA_MAX_QUERY_MILLIS);

                queryTimeoutMillis = s == null ? -1L : StringUtil.toLong(s);
            }

            final JettyResponseListener listener = new JettyResponseListener(request, queryTimeoutMillis);

            // Note: Send with a listener is non-blocking.
            request.send(listener);

            return listener;

        } catch (Throwable t) {
            /*
             * If something goes wrong, then close the http connection.
             * Otherwise, the connection will be closed by the caller.
             */
            try {

                if (request != null)
                    request.abort(t);

            } catch (Throwable t2) {
                log.warn(t2); // ignored.
            }
            throw new RuntimeException(requestURL + " : " + t, t);
        }

    }

    public Request newRequest(final String uri, final String method) {

        if (httpClient == null)
            throw new IllegalArgumentException();

        assertHttpClientRunning();

        return httpClient.newRequest(uri).method(getMethod(method));

    }

    private void assertHttpClientRunning() {

        if (httpClient.isStopped() || httpClient.isStopping())
            throw new IllegalStateException("The HTTPClient has been stopped");

    }

    HttpMethod getMethod(final String method) {
        if (method.equals("GET")) {
            return HttpMethod.GET;
        } else if (method.equals("POST")) {
            return HttpMethod.POST;
        } else if (method.equals("DELETE")) {
            return HttpMethod.DELETE;
        } else if (method.equals("PUT")) {
            return HttpMethod.PUT;
        } else {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Return the {@link ConnectOptions} which will be used by default for the
     * SPARQL end point for a QUERY or other idempotent operation.
     * 
     * @param sparqlEndpointURL
     *            The SPARQL end point.
     * @param uuid
     *            The unique identifier for the request that may be used to
     *            CANCEL the request.
     * @param tx
     *            A transaction that will isolate the operation (optional).
     */
    final protected ConnectOptions newQueryConnectOptions(final String sparqlEndpointURL, final UUID uuid,
            final IRemoteTx tx) {

        final ConnectOptions opts = newConnectOptions(sparqlEndpointURL, uuid, tx);

        opts.method = getQueryMethod();

        opts.update = false;

        return opts;

    }

    /**
     * Return the {@link ConnectOptions} which will be used by default for the
     * SPARQL end point for an UPDATE or other non-idempotant operation.
     * 
     * @param sparqlEndpointURL
     *            The SPARQL end point.
     * @param uuid
     *            The unique identifier for the request that may be used to
     *            CANCEL the request.
     * @param tx
     *            A transaction that will isolate the operation (optional).
     */
    final protected ConnectOptions newUpdateConnectOptions(final String sparqlEndpointURL, final UUID uuid,
            final IRemoteTx tx) {

        final ConnectOptions opts = newConnectOptions(sparqlEndpointURL, uuid, tx);

        opts.method = "POST";

        opts.update = true;

        return opts;

    }

    // /**
    // * Return the {@link ConnectOptions} which will be used by default for the
    // * SPARQL end point.
    // */
    // final protected ConnectOptions newConnectOptions() {
    //
    // return mgr.newConnectOptions(sparqlEndpointURL);
    //
    // }

    /**
     * Return the {@link ConnectOptions} which will be used by default for the
     * specified service URL.
     * <p>
     * There are three cases:
     * <dl>
     * <dt>The operation is not isolated by a transaction</dt>
     * <dd>This will return a {@link RemoteRepository} that DOES NOT specify a
     * timestamp to be used for read or write operations. For read operations,
     * this will cause it to use the default view of the namespace (as
     * configured on the server) and that will always be non-blocking (either
     * reading against the then current lastCommitTime on the database or
     * reading against an explicit read lock). For write operations, this will
     * cause it to use the UNISOLATED view of the namespace.</dd>
     * <dt>The operation is isolated by a read/write transaction</dt>
     * <dd>This will return a {@link RemoteRepository} which specifies the
     * transaction identifier (txId) for both read and write operations. This
     * ensures that they both have the same view of the write set of the
     * transaction (we can not use the readsOnCommitTime for read operations
     * because writes on the transaction are not visible unless we use the
     * txId).</dd>
     * <dt>The operation is isolated by a read-only transaction</dt>
     * <dd>This will return a {@link RemoteRepository} which use the
     * readsOnCommitTime for the transaction. This provides snapshot isolation
     * without any overhead and is also compatible with HA (where the
     * transaction management is performed on the leader and the followers are
     * not be aware of the txIds)</dd>
     * </dl>
     * 
     * @param serviceURL
     *            The URL of the service for the request.
     * @param uuid
     *            The unique identifier for the request that may be used to
     *            CANCEL the request.
     * @param tx
     *            A transaction that will isolate the operation (optional).
     */
    ConnectOptions newConnectOptions(final String serviceURL, final UUID uuid, final IRemoteTx tx) {

        final ConnectOptions opts = new ConnectOptions(serviceURL);

        if (tx != null) {

            /*
             * Some kind of transaction.
             */

            if (tx.isReadOnly()) {

                /*
                 * A read-only transaction.
                 * 
                 * FIXME This will not work for scale-out. We need to specify
                 * the txId itself.
                 */
                opts.addRequestParam("timestamp", Long.toString(tx.getReadsOnCommitTime()));

            } else {

                /*
                 * A read/write transaction. We must use the txId to have the
                 * correct isolation.
                 */

                opts.addRequestParam("timestamp", Long.toString(tx.getTxId()));

            }

        }

        if (uuid != null) {
            /**
             * Associate requests with a UUID so they may be cancelled.
             * 
             * @see #1254 (All REST API operations should be cancelable from
             *      both REST API and workbench.)
             */
            opts.addRequestParam(QUERYID, uuid.toString());
        }

        return opts;

    }

    /**
     * Builds a graph from an RDF result set (statements, not binding sets).
     * 
     * @param response
     *            The connection from which to read the results.
     * 
     * @return The graph
     * 
     * @throws Exception
     *             If anything goes wrong.
     */
    GraphQueryResult graphResults(final ConnectOptions opts, final UUID queryId,
            final IPreparedQueryListener listener) throws Exception {

        // The listener handling the http response.
        JettyResponseListener response = null;
        // Incrementally parse the response in another thread.
        BackgroundGraphResult result = null;
        try {

            response = doConnect(opts);

            checkResponseCode(response);

            final String baseURI = "";

            final String contentType = response.getContentType();

            if (contentType == null)
                throw new RuntimeException("Not found: Content-Type");

            final MiniMime mimeType = new MiniMime(contentType);

            final RDFFormat format = RDFFormat.forMIMEType(mimeType.getMimeType());

            if (format == null)
                throw new IOException(
                        "Could not identify format for service response: serviceURI=" + opts.getBestRequestURL()
                                + ", contentType=" + contentType + " : response=" + response.getResponseBody());

            final RDFParserFactory factory = RDFParserRegistry.getInstance().get(format);

            if (factory == null)
                throw new RuntimeException(
                        "RDFParserFactory not found: Content-Type=" + contentType + ", format=" + format);

            final RDFParser parser = factory.getParser();

            // TODO See #1055 (Make RDFParserOptions configurable)
            parser.setValueFactory(new ValueFactoryImpl());

            parser.setVerifyData(true);

            parser.setStopAtFirstError(true);

            parser.setDatatypeHandling(RDFParser.DatatypeHandling.IGNORE);
            /**
             * Note: The default charset depends on the MIME Type. The [charset]
             * MUST be [null] if the MIME Type is binary since this effects
             * whether a Reader or InputStream will be used to construct and
             * apply the RDF parser.
             * 
             * @see <a href="http://trac.blazegraph.com/ticket/920" > Content
             *      negotiation orders accept header scores in reverse </a>
             */
            Charset charset = format.getCharset();// Charset.forName(UTF8);
            try {

                final String encoding = response.getContentEncoding();
                if (encoding != null)
                    charset = Charset.forName(encoding);
            } catch (IllegalCharsetNameException e) {
                // work around for Joseki-3.2
                // Content-Type: application/rdf+xml;
                // charset=application/rdf+xml
            }

            final BackgroundGraphResult tmp = new BackgroundGraphResult(parser, response.getInputStream(), charset,
                    baseURI) {

                final AtomicBoolean notDone = new AtomicBoolean(true);

                @Override
                public boolean hasNext() throws QueryEvaluationException {

                    final boolean hasNext = super.hasNext();

                    if (hasNext == false) {

                        notDone.set(false);

                    }

                    return hasNext;

                }

                @Override
                public void close() throws QueryEvaluationException {

                    try {

                        super.close();

                    } finally {

                        if (notDone.compareAndSet(true, false)) {

                            try {
                                cancel(queryId);
                            } catch (Exception ex) {
                                log.warn(ex);
                            }

                        }

                        if (listener != null) {
                            listener.closed(queryId);
                        }

                    }

                };

            };

            /*
             * Note: Asynchronous execution. Typically does not even start
             * running until after we leave this method!
             */
            executor.execute(tmp);

            // The executor accepted the task for execution (at some point).
            result = tmp;

            /*
             * Result will be asynchronously produced.
             * 
             * Note: At this point the caller is responsible for calling close()
             * on this object to clean up the resources associated with this
             * request.
             */
            return result;

        } finally {

            if (response != null && result == null) {
                /*
                 * This code path only handles errors. We have a response, but
                 * we were not able to generate the asynchronous [result]
                 * object.
                 */
                response.abort();

                try {
                    /*
                     * POST back to the server in an attempt to cancel the
                     * request if already executing on the server.
                     */
                    cancel(queryId);
                } catch (Exception ex) {
                    log.warn(ex);
                }

                if (listener != null) {
                    listener.closed(queryId);
                }
            }

        }

    }

    /**
     * Processing the response for a SPARQL UPDATE request.
     * <p>
     * Note: This is not compatible with the MONITOR option. That option
     * requires the client to parse the response body to figure out whether or
     * not the UPDATE operation was successful.
     * 
     * @param response
     *            The connection from which to read the results.
     * 
     * @throws Exception
     *             If anything goes wrong.
     * 
     * @see <a href="http://trac.bigdata.com/ticket/1255" > RemoteRepository
     *      does not CANCEL a SPARQL UPDATE if there is a client error </a>
     */
    void sparqlUpdateResults(final ConnectOptions opts, final UUID queryId, final IPreparedQueryListener listener)
            throws Exception {

        JettyResponseListener response = null;
        try {

            // Note: No response body is expected.

            response = doConnect(opts);

            checkResponseCode(response);

        } finally {

            if (response == null) {
                try {
                    /*
                     * Some error prevented our obtaining a response.
                     * 
                     * POST back to the server in an attempt to cancel the
                     * request if already executing on the server.
                     */
                    cancel(queryId);
                } catch (Exception ex) {
                    log.warn(ex);
                }
            } else {
                /*
                 * Note: We are not reading anything from the response so I
                 * THINK that we do not need to call listener.abort(). If we do
                 * need to call this, then we might need to distinguish between
                 * a normal response and when we read the response entity.
                 */
                // response.abort();
            }
            if (listener != null) {
                // Notify client-side listener.
                listener.closed(queryId);
            }

        }

    }

    /**
     * Cancel a query running remotely on the server.
     * 
     * @param queryID
     *            the UUID of the query to cancel
     */
    public void cancel(final UUID queryId) throws Exception {

        if (queryId == null)
            return;

        /*
         * Note: The CANCEL request reuses the same parameter name ("queryId")
         * to identify the UUIDs of the request(s) that should be cancelled.
         * This means that we need to be careful on the server that the CANCEL
         * request does not get registered under the UUID of the request to be
         * canceled and thus cause itself to be cancelled....
         */
        final ConnectOptions opts = newUpdateConnectOptions(baseServiceURL, queryId, null/* txId */);

        opts.addRequestParam("cancelQuery");

        // Note: handled above.
        // opts.addRequestParam(QUERYID, queryId.toString());

        JettyResponseListener response = null;
        try {
            // Issue request, check response status code.
            checkResponseCode(response = doConnect(opts));
        } finally {
            /*
             * Ensure that the http response entity is consumed so that the http
             * connection will be released in a timely fashion.
             */
            if (response != null)
                response.abort();

        }

    }

    /**
     * List the currently running queries on the server
     * 
     */
    public Collection<RunningQuery> showQueries() throws Exception {

        final ConnectOptions opts = newUpdateConnectOptions(baseServiceURL, null, null/* txId */);

        opts.addRequestParam(SHOW_QUERIES);

        opts.setAcceptHeader(IMimeTypes.MIME_APPLICATION_JSON);

        JettyResponseListener response = null;

        try {
            // Issue request, check response status code.
            checkResponseCode(response = doConnect(opts));

            final String contentType = response.getContentType();

            if (!IMimeTypes.MIME_APPLICATION_JSON.equals(contentType))
                throw new RuntimeException("Expected MIME_TYPE " + IMimeTypes.MIME_APPLICATION_JSON
                        + " but received : " + contentType + ".");

            final InputStream is = response.getInputStream();

            final List<RunningQuery> runningQueries = JsonHelper.readRunningQueryList(is);

            return runningQueries;

        } finally {
            /*
             * Ensure that the http response entity is consumed so that the http
             * connection will be released in a timely fashion.
             */
            if (response != null)
                response.abort();

        }

    }

    /**
     * Extracts the solutions from a SPARQL query.
     * 
     * @param response
     *            The connection from which to read the results.
     * @param listener
     *            The listener to notify when the query result has been closed
     *            (optional).
     * 
     * @return The results.
     * 
     * @throws Exception
     *             If anything goes wrong.
     */
    public TupleQueryResult tupleResults(final ConnectOptions opts, final UUID queryId,
            final IPreparedQueryListener listener) throws Exception {

        // listener handling the http response.
        JettyResponseListener response = null;
        // future for parsing that response (in the background).
        FutureTask<Void> ft = null;
        // iteration pattern returned to caller. once they hold this they are
        // responsible for cleaning up the request by calling close().
        TupleQueryResultImpl tqrImpl = null;
        try {

            response = doConnect(opts);

            checkResponseCode(response);

            final String contentType = response.getContentType();

            final MiniMime mimeType = new MiniMime(contentType);

            final TupleQueryResultFormat format = TupleQueryResultFormat.forMIMEType(mimeType.getMimeType());

            if (format == null)
                throw new IOException(
                        "Could not identify format for service response: serviceURI=" + opts.getBestRequestURL()
                                + ", contentType=" + contentType + " : response=" + response.getResponseBody());

            final TupleQueryResultParserFactory parserFactory = TupleQueryResultParserRegistry.getInstance()
                    .get(format);

            if (parserFactory == null)
                throw new IOException("No parser for format for service response: serviceURI="
                        + opts.getBestRequestURL() + ", contentType=" + contentType + ", format=" + format
                        + " : response=" + response.getResponseBody());

            final TupleQueryResultParser parser = parserFactory.getParser();

            final BackgroundTupleResult result = new BackgroundTupleResult(parser, response.getInputStream());

            final MapBindingSet bindings = new MapBindingSet();

            final InsertBindingSetCursor cursor = new InsertBindingSetCursor(result, bindings);

            // Wrap as FutureTask so we can cancel.
            ft = new FutureTask<Void>(result, null/* result */);

            /*
             * Submit task for execution. It will asynchronously consume the
             * response, pumping solutions into the cursor.
             * 
             * Note: Can throw a RejectedExecutionException!
             */
            executor.execute(ft);

            /*
             * Note: This will block until the binding names are received, so it
             * can not be done until we submit the BackgroundTupleResult for
             * execution.
             */
            final List<String> list = new ArrayList<String>(result.getBindingNames());

            /*
             * The task was accepted by the executor. Wrap with iteration
             * pattern. Once this object is returned to the caller they are
             * responsible for calling close() to provide proper error cleanup
             * of the resources associated with the request.
             */
            final TupleQueryResultImpl tmp = new TupleQueryResultImpl(list, cursor) {

                private final AtomicBoolean notDone = new AtomicBoolean(true);

                @Override
                public boolean hasNext() throws QueryEvaluationException {

                    final boolean hasNext = super.hasNext();

                    if (hasNext == false) {

                        notDone.set(false);

                    }

                    return hasNext;

                }

                @Override
                public void handleClose() throws QueryEvaluationException {

                    try {

                        super.handleClose();

                    } finally {

                        if (notDone.compareAndSet(true, false)) {

                            try {
                                cancel(queryId);
                            } catch (Exception ex) {
                                log.warn(ex);
                            }

                        }

                        /*
                         * Notify the listener.
                         */
                        if (listener != null) {
                            listener.closed(queryId);
                        }

                    }

                };

            };

            /*
             * Return the tuple query result listener to the caller. They now
             * have responsibility for calling close() on that object in order
             * to close the http connection and release the associated
             * resources.
             */
            return (tqrImpl = tmp);

        } finally {

            if (response != null && tqrImpl == null) {
                /*
                 * Error handling code path. We have an http response listener
                 * but we were not able to setup the tuple query result
                 * listener.
                 */
                if (ft != null) {
                    /*
                     * We submitted the task to parse the response. Since the
                     * code is not returning normally (tqrImpl:=null) we cancel
                     * the FutureTask for the background parse of that response.
                     */
                    ft.cancel(true/* mayInterruptIfRunning */);
                }
                // Abort the http response handling.
                response.abort();
                try {
                    /*
                     * POST back to the server to cancel the request in case it
                     * is still running on the server.
                     */
                    cancel(queryId);
                } catch (Exception ex) {
                    log.warn(ex);
                }
                if (listener != null) {
                    listener.closed(queryId);
                }
            }

        }

    }

    /**
     * Parse a SPARQL result set for an ASK query.
     * 
     * @param response
     *            The connection from which to read the results.
     * 
     * @return <code>true</code> or <code>false</code> depending on what was
     *         encoded in the SPARQL result set.
     * 
     * @throws Exception
     *             If anything goes wrong, including if the result set does not
     *             encode a single boolean value.
     */
    public boolean booleanResults(final ConnectOptions opts, final UUID queryId,
            final IPreparedQueryListener listener) throws Exception {

        JettyResponseListener response = null;
        Boolean result = null;
        try {

            response = doConnect(opts);

            checkResponseCode(response);

            final String contentType = response.getContentType();

            final MiniMime mimeType = new MiniMime(contentType);

            final BooleanQueryResultFormat format = BooleanQueryResultFormat.forMIMEType(mimeType.getMimeType());

            if (format == null)
                throw new IOException(
                        "Could not identify format for service response: serviceURI=" + opts.getBestRequestURL()
                                + ", contentType=" + contentType + " : response=" + response.getResponseBody());

            final BooleanQueryResultParserFactory factory = BooleanQueryResultParserRegistry.getInstance()
                    .get(format);

            if (factory == null)
                throw new RuntimeException("No factory for Content-Type: " + contentType);

            final BooleanQueryResultParser parser = factory.getParser();

            final InputStream is = response.getInputStream();
            try {
                result = parser.parse(is);
                return result;
            } finally {
                is.close();
            }

        } finally {

            if (result == null) {
                /*
                 * Error handling path. We issued the request, but were not able
                 * to parse out the response.
                 */
                if (response != null) {
                    // Make sure the response listener is closed.
                    response.abort();
                }
                try {
                    /*
                     * POST request to server to cancel query in case it is
                     * still running.
                     */
                    cancel(queryId);
                } catch (Exception ex) {
                    log.warn(ex);
                }
            }

            if (listener != null) {
                listener.closed(queryId);
            }

        }

    }

    // /**
    // * Counts the #of results in a SPARQL result set.
    // *
    // * @param response
    // * The connection from which to read the results.
    // *
    // * @return The #of results.
    // *
    // * @throws Exception
    // * If anything goes wrong.
    // */
    // protected long countResults(final JettyResponseListener response) throws
    // Exception {
    //
    // try {
    //
    // final String contentType = response.getContentType();
    //
    // final MiniMime mimeType = new MiniMime(contentType);
    //
    // final TupleQueryResultFormat format = TupleQueryResultFormat
    // .forMIMEType(mimeType.getMimeType());
    //
    // if (format == null)
    // throw new IOException(
    // "Could not identify format for service response: serviceURI="
    // + sparqlEndpointURL + ", contentType=" + contentType
    // + " : response=" + response.getResponseBody());
    //
    // final TupleQueryResultParserFactory factory =
    // TupleQueryResultParserRegistry
    // .getInstance().get(format);
    //
    // if (factory == null)
    // throw new RuntimeException("No factory for Content-Type: " +
    // contentType);
    //
    // final TupleQueryResultParser parser = factory.getParser();
    //
    // final AtomicLong nsolutions = new AtomicLong();
    //
    // parser.setTupleQueryResultHandler(new TupleQueryResultHandlerBase() {
    // // Indicates the end of a sequence of solutions.
    // @Override
    // public void endQueryResult() {
    // // connection close is handled in finally{}
    // }
    //
    // // Handles a solution.
    // @Override
    // public void handleSolution(final BindingSet bset) {
    // if (log.isDebugEnabled())
    // log.debug(bset.toString());
    // nsolutions.incrementAndGet();
    // }
    //
    // // Indicates the start of a sequence of Solutions.
    // @Override
    // public void startQueryResult(List<String> bindingNames) {
    // }
    // });
    //
    // parser.parse(response.getInputStream());
    //
    // if (log.isInfoEnabled())
    // log.info("nsolutions=" + nsolutions);
    //
    // // done.
    // return nsolutions.longValue();
    //
    // } finally {
    //
    // if (response != null) {
    // response.abort();
    // }
    //
    // }
    //
    // }

}