org.eclipse.rdf4j.http.client.SPARQLProtocolSession.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.rdf4j.http.client.SPARQLProtocolSession.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *******************************************************************************/
package org.eclipse.rdf4j.http.client;

import static org.eclipse.rdf4j.http.protocol.Protocol.ACCEPT_PARAM_NAME;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.eclipse.rdf4j.RDF4JConfigException;
import org.eclipse.rdf4j.RDF4JException;
import org.eclipse.rdf4j.http.protocol.Protocol;
import org.eclipse.rdf4j.http.protocol.UnauthorizedException;
import org.eclipse.rdf4j.http.protocol.error.ErrorInfo;
import org.eclipse.rdf4j.http.protocol.error.ErrorType;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.query.Binding;
import org.eclipse.rdf4j.query.Dataset;
import org.eclipse.rdf4j.query.GraphQueryResult;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.QueryInterruptedException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.QueryResultHandlerException;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.query.TupleQueryResultHandler;
import org.eclipse.rdf4j.query.TupleQueryResultHandlerException;
import org.eclipse.rdf4j.query.UnsupportedQueryLanguageException;
import org.eclipse.rdf4j.query.resultio.BooleanQueryResultFormat;
import org.eclipse.rdf4j.query.resultio.BooleanQueryResultParser;
import org.eclipse.rdf4j.query.resultio.BooleanQueryResultParserRegistry;
import org.eclipse.rdf4j.query.resultio.QueryResultFormat;
import org.eclipse.rdf4j.query.resultio.QueryResultIO;
import org.eclipse.rdf4j.query.resultio.QueryResultParseException;
import org.eclipse.rdf4j.query.resultio.TupleQueryResultFormat;
import org.eclipse.rdf4j.query.resultio.TupleQueryResultParser;
import org.eclipse.rdf4j.query.resultio.TupleQueryResultParserRegistry;
import org.eclipse.rdf4j.query.resultio.helpers.QueryResultCollector;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.rio.ParserConfig;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFHandler;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.RDFParserRegistry;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.UnsupportedRDFormatException;
import org.eclipse.rdf4j.rio.helpers.BasicParserSettings;
import org.eclipse.rdf4j.rio.helpers.ParseErrorLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;

/**
 * The SPARQLProtocolSession provides low level HTTP methods for communication with SPARQL endpoints. All
 * methods are compliant to the <a href="https://www.w3.org/TR/sparql11-protocol/">SPARQL 1.1 Protocol W3C
 * Recommendation</a>.
 * <p/>
 * For both Tuple and Graph queries there is a variant which parses the result in the background, see
 * {@link BackgroundTupleResult} and {@link BackgroundGraphResult}. For boolean queries the result is parsed
 * in the current thread. All methods in this class guarantee that HTTP connections are closed properly and
 * returned to the connection pool. The methods in this class are not guaranteed to be thread-safe.
 * <p/>
 * Functionality specific to the RDF4J HTTP protocol can be found in {@link RDF4JProtocolSession} (which is
 * used by HTTPRepository).
 * 
 * @author Herko ter Horst
 * @author Arjohn Kampman
 * @author Andreas Schwarte
 * @author Jeen Broekstra
 * @see RDF4JProtocolSession
 * @see <a href="https://www.w3.org/TR/sparql11-protocol/">SPARQL 1.1 Protocol (W3C Recommendation)</a>
 */
public class SPARQLProtocolSession implements HttpClientDependent {

    /*-----------*
     * Constants *
     *-----------*/

    protected static final Charset UTF8 = Charset.forName("UTF-8");

    /**
     * The default value of the threshold for URL length, beyond which we use the POST method for SPARQL query
     * requests. The default is based on the lowest common denominator for various web servers.
     */
    public static final int DEFAULT_MAXIMUM_URL_LENGTH = 4083;

    /**
     * @deprecated use {@link #DEFAULT_MAXIMUM_URL_LENGTH} instead.
     */
    @Deprecated
    public static final int MAXIMUM_URL_LENGTH = DEFAULT_MAXIMUM_URL_LENGTH;

    /**
     * System property for configuration of URL length threshold: {@code rdf4j.sparql.url.maxlength}. A
     * threshold of 0 (or a negative value) means that the POST method is used for <strong>every</strong>
     * SPARQL query request.
     */
    public static final String MAXIMUM_URL_LENGTH_PARAM = "rdf4j.sparql.url.maxlength";

    /**
     * The threshold for URL length, beyond which we use the POST method. A threshold of 0 (or a negative
     * value) means that the POST method is used for <strong>every</strong> SPARQL query request.
     */
    private final int maximumUrlLength;

    final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * shared instance of a {@link Joiner} for creating a comma-separated string.
     */
    private static final Joiner commaJoiner = Joiner.on(", ");

    /*-----------*
     * Variables *
     *-----------*/

    private ValueFactory valueFactory;

    private String queryURL;

    private String updateURL;

    private HttpClient httpClient;

    private final ExecutorService executor;

    private final HttpClientContext httpContext;

    private HttpParams params;

    private ParserConfig parserConfig = new ParserConfig();

    private TupleQueryResultFormat preferredTQRFormat = TupleQueryResultFormat.BINARY;

    private BooleanQueryResultFormat preferredBQRFormat = BooleanQueryResultFormat.TEXT;

    private RDFFormat preferredRDFFormat = RDFFormat.TURTLE;

    private Map<String, String> additionalHttpHeaders = Collections.emptyMap();

    /*--------------*
     * Constructors *
     *--------------*/

    public SPARQLProtocolSession(HttpClient client, ExecutorService executor) {
        this.httpClient = client;
        this.httpContext = new HttpClientContext();
        this.executor = executor;
        valueFactory = SimpleValueFactory.getInstance();
        httpContext.setCookieStore(new BasicCookieStore());

        // parser used for processing server response data should be lenient
        parserConfig.addNonFatalError(BasicParserSettings.VERIFY_DATATYPE_VALUES);
        parserConfig.addNonFatalError(BasicParserSettings.VERIFY_LANGUAGE_TAGS);

        // configure the maximum url length for SPARQL query GET requests
        int maximumUrlLength = DEFAULT_MAXIMUM_URL_LENGTH;
        String propertyValue = System.getProperty(MAXIMUM_URL_LENGTH_PARAM);
        if (propertyValue != null) {
            try {
                maximumUrlLength = Integer.parseInt(propertyValue);
            } catch (NumberFormatException e) {
                throw new RDF4JConfigException("integer value expected for property " + MAXIMUM_URL_LENGTH_PARAM,
                        e);
            }
        }
        this.maximumUrlLength = maximumUrlLength;
    }

    /*-----------------*
     * Get/set methods *
     *-----------------*/

    public final HttpClient getHttpClient() {
        return httpClient;
    }

    public void setHttpClient(HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public void setValueFactory(ValueFactory valueFactory) {
        this.valueFactory = valueFactory;
    }

    public ValueFactory getValueFactory() {
        return valueFactory;
    }

    protected void setQueryURL(String queryURL) {
        if (queryURL == null) {
            throw new IllegalArgumentException("queryURL must not be null");
        }
        this.queryURL = queryURL;
    }

    protected void setUpdateURL(String updateURL) {
        if (updateURL == null) {
            throw new IllegalArgumentException("updateURL must not be null");
        }
        this.updateURL = updateURL;
    }

    /**
     * Sets the preferred format for encoding tuple query results. The {@link TupleQueryResultFormat#BINARY
     * binary} format is preferred by default.
     * 
     * @param format
     *        The preferred {@link TupleQueryResultFormat}, or <tt>null</tt> to indicate no specific format is
     *        preferred.
     */
    public void setPreferredTupleQueryResultFormat(TupleQueryResultFormat format) {
        preferredTQRFormat = format;
    }

    /**
     * Gets the preferred {@link TupleQueryResultFormat} for encoding tuple query results.
     * 
     * @return The preferred format, of <tt>null</tt> if no specific format is preferred.
     */
    public TupleQueryResultFormat getPreferredTupleQueryResultFormat() {
        return preferredTQRFormat;
    }

    /**
     * Sets the preferred format for encoding RDF documents. The {@link RDFFormat#TURTLE Turtle} format is
     * preferred by default.
     * 
     * @param format
     *        The preferred {@link RDFFormat}, or <tt>null</tt> to indicate no specific format is preferred.
     */
    public void setPreferredRDFFormat(RDFFormat format) {
        preferredRDFFormat = format;
    }

    /**
     * Gets the preferred {@link RDFFormat} for encoding RDF documents.
     * 
     * @return The preferred format, of <tt>null</tt> if no specific format is preferred.
     */
    public RDFFormat getPreferredRDFFormat() {
        return preferredRDFFormat;
    }

    /**
     * Sets the preferred format for encoding boolean query results. The {@link BooleanQueryResultFormat#TEXT
     * binary} format is preferred by default.
     * 
     * @param format
     *        The preferred {@link BooleanQueryResultFormat}, or <tt>null</tt> to indicate no specific format
     *        is preferred.
     */
    public void setPreferredBooleanQueryResultFormat(BooleanQueryResultFormat format) {
        preferredBQRFormat = format;
    }

    /**
     * Gets the preferred {@link BooleanQueryResultFormat} for encoding boolean query results.
     * 
     * @return The preferred format, of <tt>null</tt> if no specific format is preferred.
     */
    public BooleanQueryResultFormat getPreferredBooleanQueryResultFormat() {
        return preferredBQRFormat;
    }

    /**
     * Set the username and password for authentication with the remote server.
     * 
     * @param username
     *        the username
     * @param password
     *        the password
     */
    public void setUsernameAndPassword(String username, String password) {
        setUsernameAndPasswordForUrl(username, password, getQueryURL());
    }

    protected void setUsernameAndPasswordForUrl(String username, String password, String url) {

        if (username != null && password != null) {
            logger.debug("Setting username '{}' and password for server at {}.", username, url);
            java.net.URI requestURI = java.net.URI.create(url);
            String host = requestURI.getHost();
            int port = requestURI.getPort();
            AuthScope scope = new AuthScope(host, port);
            UsernamePasswordCredentials cred = new UsernamePasswordCredentials(username, password);
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(scope, cred);
            httpContext.setCredentialsProvider(credsProvider);
            AuthCache authCache = new BasicAuthCache();
            BasicScheme basicAuth = new BasicScheme();
            HttpHost httpHost = new HttpHost(requestURI.getHost(), requestURI.getPort(), requestURI.getScheme());
            authCache.put(httpHost, basicAuth);
            httpContext.setAuthCache(authCache);
        } else {
            httpContext.removeAttribute(HttpClientContext.AUTH_CACHE);
            httpContext.removeAttribute(HttpClientContext.CREDS_PROVIDER);
        }
    }

    protected void execute(Runnable command) {
        executor.execute(command);
    }

    public String getQueryURL() {
        return queryURL;
    }

    public String getUpdateURL() {
        return updateURL;
    }

    /*------------------*
     * Query evaluation *
     *------------------*/

    public TupleQueryResult sendTupleQuery(QueryLanguage ql, String query, Dataset dataset, boolean includeInferred,
            Binding... bindings) throws IOException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {
        return sendTupleQuery(ql, query, null, dataset, includeInferred, 0, bindings);
    }

    public TupleQueryResult sendTupleQuery(QueryLanguage ql, String query, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, Binding... bindings) throws IOException, RepositoryException,
            MalformedQueryException, UnauthorizedException, QueryInterruptedException {
        HttpUriRequest method = getQueryMethod(ql, query, baseURI, dataset, includeInferred, maxQueryTime,
                bindings);
        return getBackgroundTupleQueryResult(method);
    }

    public void sendTupleQuery(QueryLanguage ql, String query, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, TupleQueryResultHandler handler, Binding... bindings)
            throws IOException, TupleQueryResultHandlerException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {
        HttpUriRequest method = getQueryMethod(ql, query, baseURI, dataset, includeInferred, maxQueryTime,
                bindings);
        getTupleQueryResult(method, handler);
    }

    public void sendUpdate(QueryLanguage ql, String update, String baseURI, Dataset dataset,
            boolean includeInferred, Binding... bindings) throws IOException, RepositoryException,
            MalformedQueryException, UnauthorizedException, QueryInterruptedException {
        sendUpdate(ql, update, baseURI, dataset, includeInferred, 0, bindings);
    }

    public void sendUpdate(QueryLanguage ql, String update, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, Binding... bindings) throws IOException, RepositoryException,
            MalformedQueryException, UnauthorizedException, QueryInterruptedException {
        HttpUriRequest method = getUpdateMethod(ql, update, baseURI, dataset, includeInferred, maxQueryTime,
                bindings);

        try {
            executeNoContent(method);
        } catch (RepositoryException e) {
            throw e;
        } catch (MalformedQueryException e) {
            throw e;
        } catch (QueryInterruptedException e) {
            throw e;
        } catch (RDF4JException e) {
            throw new RepositoryException(e);
        }
    }

    public GraphQueryResult sendGraphQuery(QueryLanguage ql, String query, Dataset dataset, boolean includeInferred,
            Binding... bindings) throws IOException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {
        return sendGraphQuery(ql, query, null, dataset, includeInferred, 0, bindings);
    }

    public GraphQueryResult sendGraphQuery(QueryLanguage ql, String query, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, Binding... bindings) throws IOException, RepositoryException,
            MalformedQueryException, UnauthorizedException, QueryInterruptedException {
        try {
            HttpUriRequest method = getQueryMethod(ql, query, baseURI, dataset, includeInferred, maxQueryTime,
                    bindings);
            return getRDFBackground(method, false);
        } catch (RDFHandlerException e) {
            // Found a bug in TupleQueryResultBuilder?
            throw new RepositoryException(e);
        }
    }

    public void sendGraphQuery(QueryLanguage ql, String query, Dataset dataset, boolean includeInferred,
            RDFHandler handler, Binding... bindings) throws IOException, RDFHandlerException, RepositoryException,
            MalformedQueryException, UnauthorizedException, QueryInterruptedException {
        sendGraphQuery(ql, query, null, dataset, includeInferred, 0, handler, bindings);
    }

    public void sendGraphQuery(QueryLanguage ql, String query, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, RDFHandler handler, Binding... bindings)
            throws IOException, RDFHandlerException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {
        HttpUriRequest method = getQueryMethod(ql, query, baseURI, dataset, includeInferred, maxQueryTime,
                bindings);
        getRDF(method, handler, false);
    }

    public boolean sendBooleanQuery(QueryLanguage ql, String query, Dataset dataset, boolean includeInferred,
            Binding... bindings) throws IOException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {
        return sendBooleanQuery(ql, query, null, dataset, includeInferred, 0, bindings);
    }

    public boolean sendBooleanQuery(QueryLanguage ql, String query, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, Binding... bindings) throws IOException, RepositoryException,
            MalformedQueryException, UnauthorizedException, QueryInterruptedException {
        HttpUriRequest method = getQueryMethod(ql, query, baseURI, dataset, includeInferred, maxQueryTime,
                bindings);
        try {
            return getBoolean(method);
        } catch (RepositoryException e) {
            throw e;
        } catch (MalformedQueryException e) {
            throw e;
        } catch (QueryInterruptedException e) {
            throw e;
        } catch (RDF4JException e) {
            throw new RepositoryException(e);
        }
    }

    /**
     * Get the additional HTTP headers which will be used
     * 
     * @return a read-only view of the additional HTTP headers which will be included in every request to the
     *         server.
     */
    public Map<String, String> getAdditionalHttpHeaders() {
        return Collections.unmodifiableMap(additionalHttpHeaders);
    }

    /**
     * Set additional HTTP headers to be included in every request to the server, which may be required for
     * certain unusual server configurations.
     * 
     * @param additionalHttpHeaders
     *        a map containing pairs of header names and values. May be null
     */
    public void setAdditionalHttpHeaders(Map<String, String> additionalHttpHeaders) {
        if (additionalHttpHeaders == null) {
            this.additionalHttpHeaders = Collections.emptyMap();
        } else {
            this.additionalHttpHeaders = additionalHttpHeaders;
        }
    }

    protected HttpUriRequest getQueryMethod(QueryLanguage ql, String query, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, Binding... bindings) {
        List<NameValuePair> queryParams = getQueryMethodParameters(ql, query, baseURI, dataset, includeInferred,
                maxQueryTime, bindings);
        HttpUriRequest method;
        String queryUrlWithParams;
        try {
            URIBuilder urib = new URIBuilder(getQueryURL());
            for (NameValuePair nvp : queryParams)
                urib.addParameter(nvp.getName(), nvp.getValue());
            queryUrlWithParams = urib.toString();
        } catch (URISyntaxException e) {
            throw new AssertionError(e);
        }
        if (shouldUsePost(queryUrlWithParams)) {
            // we just built up a URL for nothing. oh well.
            // It's probably not much overhead against
            // the poor triplestore having to process such as massive query
            HttpPost postMethod = new HttpPost(getQueryURL());
            postMethod.setHeader("Content-Type", Protocol.FORM_MIME_TYPE + "; charset=utf-8");
            postMethod.setEntity(new UrlEncodedFormEntity(queryParams, UTF8));
            method = postMethod;
        } else {
            method = new HttpGet(queryUrlWithParams);
        }
        // functionality to provide custom http headers as required by the
        // applications
        for (Map.Entry<String, String> additionalHeader : additionalHttpHeaders.entrySet()) {
            method.addHeader(additionalHeader.getKey(), additionalHeader.getValue());
        }
        return method;
    }

    /**
     * Return whether the provided query should use POST (otherwise use GET)
     * 
     * @param fullQueryUrl
     *        the complete URL, including hostname and all HTTP query parameters
     */
    protected boolean shouldUsePost(String fullQueryUrl) {
        return fullQueryUrl.length() > maximumUrlLength;
    }

    protected HttpUriRequest getUpdateMethod(QueryLanguage ql, String update, String baseURI, Dataset dataset,
            boolean includeInferred, Binding... bindings) {
        return getUpdateMethod(ql, update, baseURI, dataset, includeInferred, 0, bindings);
    }

    protected HttpUriRequest getUpdateMethod(QueryLanguage ql, String update, String baseURI, Dataset dataset,
            boolean includeInferred, int maxQueryTime, Binding... bindings) {
        HttpPost method = new HttpPost(getUpdateURL());

        method.setHeader("Content-Type", Protocol.FORM_MIME_TYPE + "; charset=utf-8");

        List<NameValuePair> queryParams = getUpdateMethodParameters(ql, update, baseURI, dataset, includeInferred,
                maxQueryTime, bindings);

        method.setEntity(new UrlEncodedFormEntity(queryParams, UTF8));

        if (this.additionalHttpHeaders != null) {
            for (Map.Entry<String, String> additionalHeader : additionalHttpHeaders.entrySet())
                method.addHeader(additionalHeader.getKey(), additionalHeader.getValue());
        }

        return method;
    }

    protected List<NameValuePair> getQueryMethodParameters(QueryLanguage ql, String query, String baseURI,
            Dataset dataset, boolean includeInferred, int maxQueryTime, Binding... bindings) {
        List<NameValuePair> queryParams = new ArrayList<NameValuePair>();

        /*
         * Only query, default-graph-uri, and named-graph-uri are standard parameters in SPARQL Protocol 1.1.
         */

        if (query != null) {
            if (baseURI != null && !baseURI.equals("")) {
                // prepend query string with base URI declaration
                query = "BASE <" + baseURI + "> \n" + query;
            }
            queryParams.add(new BasicNameValuePair(Protocol.QUERY_PARAM_NAME, query));
        }

        if (dataset != null) {
            for (IRI defaultGraphURI : dataset.getDefaultGraphs()) {
                queryParams.add(
                        new BasicNameValuePair(Protocol.DEFAULT_GRAPH_PARAM_NAME, String.valueOf(defaultGraphURI)));
            }
            for (IRI namedGraphURI : dataset.getNamedGraphs()) {
                queryParams.add(
                        new BasicNameValuePair(Protocol.NAMED_GRAPH_PARAM_NAME, String.valueOf(namedGraphURI)));
            }
        }

        return queryParams;
    }

    protected List<NameValuePair> getUpdateMethodParameters(QueryLanguage ql, String update, String baseURI,
            Dataset dataset, boolean includeInferred, Binding... bindings) {
        return getUpdateMethodParameters(ql, update, baseURI, dataset, includeInferred, 0, bindings);
    }

    protected List<NameValuePair> getUpdateMethodParameters(QueryLanguage ql, String update, String baseURI,
            Dataset dataset, boolean includeInferred, int maxQueryTime, Binding... bindings) {

        List<NameValuePair> queryParams = new ArrayList<NameValuePair>();

        if (update != null) {
            if (baseURI != null && !baseURI.equals("")) {
                // prepend update string with base URI declaration
                update = "BASE <" + baseURI + "> \n" + update;
            }
            queryParams.add(new BasicNameValuePair(Protocol.UPDATE_PARAM_NAME, update));
            logger.debug("added update string {}", update);
        }

        if (dataset != null) {
            if (dataset.getDefaultRemoveGraphs().size() > 0) {
                if (!(dataset.getDefaultRemoveGraphs().equals(dataset.getDefaultGraphs()))) {
                    logger.warn(
                            "ambiguous dataset spec for SPARQL endpoint: default graphs and default remove graphs both defined but not equal");
                }
                for (IRI graphURI : dataset.getDefaultRemoveGraphs()) {
                    if (dataset.getDefaultInsertGraph() != null) {
                        if (!dataset.getDefaultInsertGraph().equals(graphURI)) {
                            logger.warn(
                                    "ambiguous dataset spec for SPARQL endpoint: default insert graph ({}) and default remove graph ({}) both defined but not equal. ",
                                    dataset.getDefaultInsertGraph(), graphURI);
                        }
                    }
                    queryParams
                            .add(new BasicNameValuePair(Protocol.USING_GRAPH_PARAM_NAME, String.valueOf(graphURI)));
                }
            }

            if (dataset.getDefaultInsertGraph() != null) {
                if (!dataset.getDefaultGraphs().isEmpty()) {
                    if (!(dataset.getDefaultGraphs().size() == 1
                            && dataset.getDefaultGraphs().contains(dataset.getDefaultInsertGraph()))) {
                        logger.warn(
                                "ambiguous dataset spec for SPARQL endpoint: default insert graph ({}) and default graphs both defined but not equal. ",
                                dataset.getDefaultInsertGraph());
                    }
                }

                queryParams.add(new BasicNameValuePair(Protocol.USING_GRAPH_PARAM_NAME,
                        String.valueOf(dataset.getDefaultInsertGraph())));
            }

            for (IRI defaultGraphURI : dataset.getDefaultGraphs()) {
                queryParams.add(
                        new BasicNameValuePair(Protocol.USING_GRAPH_PARAM_NAME, String.valueOf(defaultGraphURI)));
            }
            for (IRI namedGraphURI : dataset.getNamedGraphs()) {
                queryParams.add(new BasicNameValuePair(Protocol.USING_NAMED_GRAPH_PARAM_NAME,
                        String.valueOf(namedGraphURI)));
            }
        }

        return queryParams;
    }

    /*------------------*
     * Response parsing *
     *------------------*/

    /**
     * Parse the response in a background thread. HTTP connections are dealt with in the
     * {@link BackgroundTupleResult} or (in the error-case) in this method.
     */
    protected BackgroundTupleResult getBackgroundTupleQueryResult(HttpUriRequest method)
            throws RepositoryException, QueryInterruptedException, MalformedQueryException, IOException {

        boolean submitted = false;

        // Specify which formats we support
        Set<QueryResultFormat> tqrFormats = TupleQueryResultParserRegistry.getInstance().getKeys();
        if (tqrFormats.isEmpty()) {
            throw new RepositoryException("No tuple query result parsers have been registered");
        }

        BackgroundTupleResult tRes = null;
        // send the tuple query
        HttpResponse response = sendTupleQueryViaHttp(method, tqrFormats);
        try {

            // if we get here, HTTP code is 200
            String mimeType = getResponseMIMEType(response);
            QueryResultFormat format = TupleQueryResultFormat.matchMIMEType(mimeType, tqrFormats).orElseThrow(
                    () -> new RepositoryException("Server responded with an unsupported file format: " + mimeType));
            TupleQueryResultParser parser = QueryResultIO.createTupleParser(format, getValueFactory());
            tRes = new BackgroundTupleResult(parser, response.getEntity().getContent());
            execute(tRes);
            submitted = true;
            return tRes;
        } finally {
            if (!submitted) {
                try {
                    if (tRes != null) {
                        tRes.close();
                    }
                } finally {
                    EntityUtils.consumeQuietly(response.getEntity());
                }
            }
        }
    }

    /**
     * Parse the response in this thread using the provided {@link TupleQueryResultHandler}. All HTTP
     * connections are closed and released in this method
     */
    protected void getTupleQueryResult(HttpUriRequest method, TupleQueryResultHandler handler)
            throws IOException, TupleQueryResultHandlerException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {
        // Specify which formats we support
        Set<QueryResultFormat> tqrFormats = TupleQueryResultParserRegistry.getInstance().getKeys();
        if (tqrFormats.isEmpty()) {
            throw new RepositoryException("No tuple query result parsers have been registered");
        }

        // send the tuple query
        HttpResponse response = sendTupleQueryViaHttp(method, tqrFormats);
        try {

            // if we get here, HTTP code is 200
            String mimeType = getResponseMIMEType(response);
            try {
                QueryResultFormat format = TupleQueryResultFormat.matchMIMEType(mimeType, tqrFormats)
                        .orElseThrow(() -> new RepositoryException(
                                "Server responded with an unsupported file format: " + mimeType));
                TupleQueryResultParser parser = QueryResultIO.createTupleParser(format, getValueFactory());
                parser.setQueryResultHandler(handler);
                parser.parseQueryResult(response.getEntity().getContent());
            } catch (QueryResultParseException e) {
                throw new RepositoryException("Malformed query result from server", e);
            } catch (QueryResultHandlerException e) {
                if (e instanceof TupleQueryResultHandlerException) {
                    throw (TupleQueryResultHandlerException) e;
                } else {
                    throw new TupleQueryResultHandlerException(e);
                }
            }
        } finally {
            EntityUtils.consumeQuietly(response.getEntity());
        }
    }

    /**
     * Send the tuple query via HTTP and throws an exception in case anything goes wrong, i.e. only for HTTP
     * 200 the method returns without exception. If HTTP status code is not equal to 200, the request is
     * aborted, however pooled connections are not released.
     * 
     * @param method
     * @throws RepositoryException
     * @throws HttpException
     * @throws IOException
     * @throws QueryInterruptedException
     * @throws MalformedQueryException
     */
    private HttpResponse sendTupleQueryViaHttp(HttpUriRequest method, Set<QueryResultFormat> tqrFormats)
            throws RepositoryException, IOException, QueryInterruptedException, MalformedQueryException {

        final List<String> acceptValues = new ArrayList<String>(tqrFormats.size());
        for (QueryResultFormat format : tqrFormats) {

            // Determine a q-value that reflects the user specified preference
            int qValue = 10;

            if (preferredTQRFormat != null && !preferredTQRFormat.equals(format)) {
                // Prefer specified format over other formats
                qValue -= 2;
            }

            for (String mimeType : format.getMIMETypes()) {
                String acceptParam = mimeType;

                if (qValue < 10) {
                    acceptParam += ";q=0." + qValue;
                }
                acceptValues.add(acceptParam);
            }
        }

        method.addHeader(ACCEPT_PARAM_NAME, commaJoiner.join(acceptValues));

        try {
            return executeOK(method);
        } catch (RepositoryException | MalformedQueryException | QueryInterruptedException e) {
            throw e;
        } catch (RDF4JException e) {
            throw new RepositoryException(e);
        }
    }

    /**
     * Parse the response in a background thread. HTTP connections are dealt with in the
     * {@link BackgroundGraphResult} or (in the error-case) in this method.
     */
    protected BackgroundGraphResult getRDFBackground(HttpUriRequest method, boolean requireContext)
            throws IOException, RDFHandlerException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {

        boolean submitted = false;

        // Specify which formats we support using Accept headers
        Set<RDFFormat> rdfFormats = RDFParserRegistry.getInstance().getKeys();
        if (rdfFormats.isEmpty()) {
            throw new RepositoryException("No tuple RDF parsers have been registered");
        }

        BackgroundGraphResult gRes = null;
        // send the tuple query
        HttpResponse response = sendGraphQueryViaHttp(method, requireContext, rdfFormats);
        try {

            // if we get here, HTTP code is 200
            String mimeType = getResponseMIMEType(response);
            RDFFormat format = RDFFormat.matchMIMEType(mimeType, rdfFormats).orElseThrow(
                    () -> new RepositoryException("Server responded with an unsupported file format: " + mimeType));
            RDFParser parser = Rio.createParser(format, getValueFactory());
            parser.setParserConfig(getParserConfig());
            parser.setParseErrorListener(new ParseErrorLogger());

            Charset charset = null;

            // SES-1793 : Do not attempt to check for a charset if the format is
            // defined not to have a charset
            // This prevents errors caused by people erroneously attaching a
            // charset to a binary formatted document
            HttpEntity entity = response.getEntity();
            if (format.hasCharset() && entity != null && entity.getContentType() != null) {
                // TODO copied from SPARQLGraphQuery repository, is this
                // required?
                try {
                    charset = ContentType.parse(entity.getContentType().getValue()).getCharset();
                } catch (IllegalCharsetNameException e) {
                    // work around for Joseki-3.2
                    // Content-Type: application/rdf+xml;
                    // charset=application/rdf+xml
                }
                if (charset == null) {
                    charset = UTF8;
                }
            }

            if (entity == null) {
                throw new RepositoryException("Server response was empty.");
            }

            String baseURI = method.getURI().toASCIIString();
            gRes = new BackgroundGraphResult(parser, entity.getContent(), charset, baseURI);
            execute(gRes);
            submitted = true;
            return gRes;
        } finally {
            if (!submitted) {
                try {
                    if (gRes != null) {
                        gRes.close();
                    }
                } finally {
                    EntityUtils.consumeQuietly(response.getEntity());
                }
            }
        }

    }

    /**
     * Parse the response in this thread using the provided {@link RDFHandler}. All HTTP connections are
     * closed and released in this method
     */
    protected void getRDF(HttpUriRequest method, RDFHandler handler, boolean requireContext)
            throws IOException, RDFHandlerException, RepositoryException, MalformedQueryException,
            UnauthorizedException, QueryInterruptedException {
        // Specify which formats we support using Accept headers
        Set<RDFFormat> rdfFormats = RDFParserRegistry.getInstance().getKeys();
        if (rdfFormats.isEmpty()) {
            throw new RepositoryException("No tuple RDF parsers have been registered");
        }

        // send the tuple query
        HttpResponse response = sendGraphQueryViaHttp(method, requireContext, rdfFormats);
        try {

            String mimeType = getResponseMIMEType(response);
            try {
                RDFFormat format = RDFFormat.matchMIMEType(mimeType, rdfFormats)
                        .orElseThrow(() -> new RepositoryException(
                                "Server responded with an unsupported file format: " + mimeType));
                RDFParser parser = Rio.createParser(format, getValueFactory());
                parser.setParserConfig(getParserConfig());
                parser.setParseErrorListener(new ParseErrorLogger());
                parser.setRDFHandler(handler);
                parser.parse(response.getEntity().getContent(), method.getURI().toASCIIString());
            } catch (RDFParseException e) {
                throw new RepositoryException("Malformed query result from server", e);
            }
        } finally {
            EntityUtils.consumeQuietly(response.getEntity());
        }
    }

    private HttpResponse sendGraphQueryViaHttp(HttpUriRequest method, boolean requireContext,
            Set<RDFFormat> rdfFormats)
            throws RepositoryException, IOException, QueryInterruptedException, MalformedQueryException {

        List<String> acceptParams = RDFFormat.getAcceptParams(rdfFormats, requireContext, getPreferredRDFFormat());

        method.addHeader(ACCEPT_PARAM_NAME, commaJoiner.join(acceptParams));

        try {
            return executeOK(method);
        } catch (RepositoryException | MalformedQueryException | QueryInterruptedException e) {
            throw e;
        } catch (RDF4JException e) {
            throw new RepositoryException(e);
        }
    }

    /**
     * Parse the response in this thread using a suitable {@link BooleanQueryResultParser}. All HTTP
     * connections are closed and released in this method
     * 
     * @throws RDF4JException
     */
    protected boolean getBoolean(HttpUriRequest method) throws IOException, RDF4JException {
        // Specify which formats we support using Accept headers
        Set<QueryResultFormat> booleanFormats = BooleanQueryResultParserRegistry.getInstance().getKeys();
        if (booleanFormats.isEmpty()) {
            throw new RepositoryException("No boolean query result parsers have been registered");
        }

        // send the tuple query
        HttpResponse response = sendBooleanQueryViaHttp(method, booleanFormats);
        try {

            // if we get here, HTTP code is 200
            String mimeType = getResponseMIMEType(response);
            try {
                QueryResultFormat format = BooleanQueryResultFormat.matchMIMEType(mimeType, booleanFormats)
                        .orElseThrow(() -> new RepositoryException(
                                "Server responded with an unsupported file format: " + mimeType));
                BooleanQueryResultParser parser = QueryResultIO.createBooleanParser(format);
                QueryResultCollector results = new QueryResultCollector();
                parser.setQueryResultHandler(results);
                parser.parseQueryResult(response.getEntity().getContent());
                return results.getBoolean();
            } catch (QueryResultParseException e) {
                throw new RepositoryException("Malformed query result from server", e);
            }
        } finally {
            EntityUtils.consumeQuietly(response.getEntity());
        }

    }

    private HttpResponse sendBooleanQueryViaHttp(HttpUriRequest method, Set<QueryResultFormat> booleanFormats)
            throws IOException, RDF4JException {

        final List<String> acceptValues = new ArrayList<>(booleanFormats.size());

        for (QueryResultFormat format : booleanFormats) {
            // Determine a q-value that reflects the user specified preference
            int qValue = 10;

            if (preferredBQRFormat != null && !preferredBQRFormat.equals(format)) {
                // Prefer specified format over other formats
                qValue -= 2;
            }

            for (String mimeType : format.getMIMETypes()) {
                String acceptParam = mimeType;

                if (qValue < 10) {
                    acceptParam += ";q=0." + qValue;
                }

                acceptValues.add(acceptParam);
            }
        }

        method.addHeader(ACCEPT_PARAM_NAME, commaJoiner.join(acceptValues));

        return executeOK(method);
    }

    /**
     * Convenience method to deal with HTTP level errors of tuple, graph and boolean queries in the same way.
     * This method aborts the HTTP connection.
     * 
     * @param method
     * @throws RDF4JException
     */
    protected HttpResponse executeOK(HttpUriRequest method) throws IOException, RDF4JException {
        boolean fail = true;
        HttpResponse response = execute(method);

        try {
            int httpCode = response.getStatusLine().getStatusCode();
            if (httpCode == HttpURLConnection.HTTP_OK || httpCode == HttpURLConnection.HTTP_NOT_AUTHORITATIVE) {
                fail = false;
                return response; // everything OK, control flow can continue
            } else {
                // trying to contact a non-SPARQL server?
                throw new RepositoryException("Failed to get server protocol; no such resource on this server: "
                        + method.getURI().toString());
            }
        } finally {
            if (fail) {
                EntityUtils.consumeQuietly(response.getEntity());
            }
        }
    }

    protected void executeNoContent(HttpUriRequest method) throws IOException, RDF4JException {
        HttpResponse response = execute(method);
        try {
            if (response.getStatusLine().getStatusCode() >= 300) {
                throw new RepositoryException("Failed to get server protocol; no such resource on this server: "
                        + method.getURI().toString());
            }
        } finally {
            EntityUtils.consume(response.getEntity());
        }
    }

    protected HttpResponse execute(HttpUriRequest method) throws IOException, RDF4JException {
        boolean consume = true;
        if (params != null) {
            method.setParams(params);
        }
        HttpResponse response = httpClient.execute(method, httpContext);

        try {
            int httpCode = response.getStatusLine().getStatusCode();
            if (httpCode >= 200 && httpCode < 300 || httpCode == HttpURLConnection.HTTP_NOT_FOUND) {
                consume = false;
                return response; // everything OK, control flow can continue
            } else {
                switch (httpCode) {
                case HttpURLConnection.HTTP_UNAUTHORIZED: // 401
                    throw new UnauthorizedException();
                case HttpURLConnection.HTTP_UNAVAILABLE: // 503
                    throw new QueryInterruptedException();
                default:
                    ErrorInfo errInfo = getErrorInfo(response);
                    // Throw appropriate exception
                    if (errInfo.getErrorType() == ErrorType.MALFORMED_DATA) {
                        throw new RDFParseException(errInfo.getErrorMessage());
                    } else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_FILE_FORMAT) {
                        throw new UnsupportedRDFormatException(errInfo.getErrorMessage());
                    } else if (errInfo.getErrorType() == ErrorType.MALFORMED_QUERY) {
                        throw new MalformedQueryException(errInfo.getErrorMessage());
                    } else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
                        throw new UnsupportedQueryLanguageException(errInfo.getErrorMessage());
                    } else if (errInfo.toString().length() > 0) {
                        throw new RepositoryException(errInfo.toString());
                    } else {
                        throw new RepositoryException(response.getStatusLine().getReasonPhrase());
                    }
                }
            }
        } finally {
            if (consume) {
                EntityUtils.consumeQuietly(response.getEntity());
            }
        }
    }

    /*-------------------------*
     * General utility methods *
     *-------------------------*/

    /**
     * Gets the MIME type specified in the response headers of the supplied method, if any. For example, if
     * the response headers contain <tt>Content-Type: application/xml;charset=UTF-8</tt>, this method will
     * return <tt>application/xml</tt> as the MIME type.
     * 
     * @param method
     *        The method to get the reponse MIME type from.
     * @return The response MIME type, or <tt>null</tt> if not available.
     */
    protected String getResponseMIMEType(HttpResponse method) throws IOException {
        Header[] headers = method.getHeaders("Content-Type");

        for (Header header : headers) {
            HeaderElement[] headerElements = header.getElements();

            for (HeaderElement headerEl : headerElements) {
                String mimeType = headerEl.getName();
                if (mimeType != null) {
                    logger.debug("reponse MIME type is {}", mimeType);
                    return mimeType;
                }
            }
        }

        return null;
    }

    protected ErrorInfo getErrorInfo(HttpResponse response) throws RepositoryException {
        try {
            ErrorInfo errInfo = ErrorInfo.parse(EntityUtils.toString(response.getEntity()));
            logger.warn("Server reports problem: {}", errInfo.getErrorMessage());
            return errInfo;
        } catch (IOException e) {
            logger.warn("Unable to retrieve error info from server");
            throw new RepositoryException("Unable to retrieve error info from server", e);
        }
    }

    /**
     * Sets the parser configuration used to process HTTP response data.
     * 
     * @param parserConfig
     *        The parserConfig to set.
     */
    public void setParserConfig(ParserConfig parserConfig) {
        this.parserConfig = parserConfig;
    }

    /**
     * @return Returns the parser configuration used to process HTTP response data.
     */
    public ParserConfig getParserConfig() {
        return parserConfig;
    }

    /**
     * Gets the http connection read timeout in milliseconds.
     */
    public long getConnectionTimeout() {
        if (params == null) {
            return 0;
        }
        return (long) params.getIntParameter(CoreConnectionPNames.SO_TIMEOUT, 0);
    }

    /**
     * Sets the http connection read timeout.
     * 
     * @param timeout
     *        timeout in milliseconds. Zero sets to infinity.
     */
    public void setConnectionTimeout(long timeout) {
        if (params == null) {
            params = new BasicHttpParams();
        }
        params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, (int) timeout);
    }
}