org.openrdf.http.client.SparqlSession Maven / Gradle / Ivy
/*
* Licensed to Aduna under one or more contributor license agreements.
* See the NOTICE.txt file distributed with this work for additional
* information regarding copyright ownership.
*
* Aduna licenses this file to you under the terms of the Aduna BSD
* License (the "License"); you may not use this file except in compliance
* with the License. See the LICENSE.txt file distributed with this work
* for the full License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
package org.openrdf.http.client;
import static org.openrdf.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.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CookieStore;
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.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
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.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.openrdf.OpenRDFException;
import org.openrdf.http.protocol.Protocol;
import org.openrdf.http.protocol.UnauthorizedException;
import org.openrdf.http.protocol.error.ErrorInfo;
import org.openrdf.http.protocol.error.ErrorType;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.query.Binding;
import org.openrdf.query.Dataset;
import org.openrdf.query.GraphQueryResult;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryInterruptedException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.QueryResultHandlerException;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.query.TupleQueryResultHandler;
import org.openrdf.query.TupleQueryResultHandlerException;
import org.openrdf.query.UnsupportedQueryLanguageException;
import org.openrdf.query.resultio.BooleanQueryResultFormat;
import org.openrdf.query.resultio.BooleanQueryResultParser;
import org.openrdf.query.resultio.BooleanQueryResultParserRegistry;
import org.openrdf.query.resultio.QueryResultIO;
import org.openrdf.query.resultio.QueryResultParseException;
import org.openrdf.query.resultio.TupleQueryResultFormat;
import org.openrdf.query.resultio.TupleQueryResultParser;
import org.openrdf.query.resultio.TupleQueryResultParserRegistry;
import org.openrdf.query.resultio.UnsupportedQueryResultFormatException;
import org.openrdf.query.resultio.helpers.QueryResultCollector;
import org.openrdf.repository.RepositoryException;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandler;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.RDFParserRegistry;
import org.openrdf.rio.Rio;
import org.openrdf.rio.UnsupportedRDFormatException;
import org.openrdf.rio.helpers.BasicParserSettings;
import org.openrdf.rio.helpers.ParseErrorLogger;
/**
* The SparqlSession provides low level HTTP methods for the HTTP communication of
* the SPARQL repository as well as the HTTP Repository. All methods are
* compliant to the SPARQL 1.1 protocol. 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. Functionality specific to the Sesame HTTP protocol can be
* found in {@link SesameSession} (which is used by Remote Repositories).
*
* The methods in this class are not guaranteed to be thread-safe.
*
* @author Herko ter Horst
* @author Arjohn Kampman
* @author Andreas Schwarte
* @see SesameSession
*/
public class SparqlSession {
/*-----------*
* Constants *
*-----------*/
protected static final Charset UTF8 = Charset.forName("UTF-8");
/**
* The threshold for URL length, beyond which we use the POST method based on
* the lowest common denominator for various web servers
*
* @since 2.8.0
*/
public static final int MAXIMUM_URL_LENGTH = 8192;
final Logger logger = LoggerFactory.getLogger(this.getClass());
/*-----------*
* Variables *
*-----------*/
private ValueFactory valueFactory;
private String queryURL;
private String updateURL;
private final HttpClient httpClient;
private final ExecutorService executor;
private final HttpContext httpContext;
private final HttpParams params = new BasicHttpParams();
private ParserConfig parserConfig = new ParserConfig();
private TupleQueryResultFormat preferredTQRFormat = TupleQueryResultFormat.BINARY;
private BooleanQueryResultFormat preferredBQRFormat = BooleanQueryResultFormat.TEXT;
private RDFFormat preferredRDFFormat = RDFFormat.TURTLE;
private Map additionalHttpHeaders = Collections.emptyMap();
/*--------------*
* Constructors *
*--------------*/
public SparqlSession(HttpClient client, ExecutorService executor) {
this.httpClient = client;
this.httpContext = new BasicHttpContext();
this.executor = executor;
valueFactory = ValueFactoryImpl.getInstance();
params.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
CookieStore cookieStore = new BasicCookieStore();
httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
params.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
// parser used for processing server response data should be lenient
parserConfig.addNonFatalError(BasicParserSettings.VERIFY_DATATYPE_VALUES);
parserConfig.addNonFatalError(BasicParserSettings.VERIFY_LANGUAGE_TAGS);
}
/*-----------------*
* Get/set methods *
*-----------------*/
protected final HttpClient getHttpClient() {
return 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 null 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 null 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 null 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 null 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 null 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 null 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.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);
params.setBooleanParameter(ClientPNames.HANDLE_AUTHENTICATION, true);
}
else {
httpContext.removeAttribute(ClientContext.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
{
HttpUriRequest method = getUpdateMethod(ql, update, baseURI, dataset, includeInferred, bindings);
try {
executeNoContent(method);
}
catch (RepositoryException e) {
throw e;
}
catch (MalformedQueryException e) {
throw e;
}
catch (QueryInterruptedException e) {
throw e;
}
catch (OpenRDFException 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 RuntimeException(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 (OpenRDFException 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 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 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 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 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() > MAXIMUM_URL_LENGTH;
}
protected HttpUriRequest getUpdateMethod(QueryLanguage ql, String update, String baseURI, Dataset dataset,
boolean includeInferred, Binding... bindings)
{
HttpPost method = new HttpPost(getUpdateURL());
method.setHeader("Content-Type", Protocol.FORM_MIME_TYPE + "; charset=utf-8");
List queryParams = getUpdateMethodParameters(ql, update, baseURI, dataset,
includeInferred, bindings);
method.setEntity(new UrlEncodedFormEntity(queryParams, UTF8));
if (this.additionalHttpHeaders != null) {
for (Map.Entry additionalHeader : additionalHttpHeaders.entrySet())
method.addHeader(additionalHeader.getKey(), additionalHeader.getValue());
}
return method;
}
protected List getQueryMethodParameters(QueryLanguage ql, String query, String baseURI,
Dataset dataset, boolean includeInferred, int maxQueryTime, Binding... bindings)
{
// TODO there is a bunch of HttpRepository specific parameters here
List queryParams = new ArrayList(bindings.length + 10);
queryParams.add(new BasicNameValuePair(Protocol.QUERY_LANGUAGE_PARAM_NAME, ql.getName()));
queryParams.add(new BasicNameValuePair(Protocol.QUERY_PARAM_NAME, query));
if (baseURI != null) {
queryParams.add(new BasicNameValuePair(Protocol.BASEURI_PARAM_NAME, baseURI));
}
queryParams.add(new BasicNameValuePair(Protocol.INCLUDE_INFERRED_PARAM_NAME,
Boolean.toString(includeInferred)));
if (maxQueryTime > 0) {
queryParams.add(new BasicNameValuePair(Protocol.TIMEOUT_PARAM_NAME, Integer.toString(maxQueryTime)));
}
if (dataset != null) {
for (URI defaultGraphURI : dataset.getDefaultGraphs()) {
queryParams.add(new BasicNameValuePair(Protocol.DEFAULT_GRAPH_PARAM_NAME,
String.valueOf(defaultGraphURI)));
}
for (URI namedGraphURI : dataset.getNamedGraphs()) {
queryParams.add(new BasicNameValuePair(Protocol.NAMED_GRAPH_PARAM_NAME,
String.valueOf(namedGraphURI)));
}
}
for (int i = 0; i < bindings.length; i++) {
String paramName = Protocol.BINDING_PREFIX + bindings[i].getName();
String paramValue = Protocol.encodeValue(bindings[i].getValue());
queryParams.add(new BasicNameValuePair(paramName, paramValue));
}
return queryParams;
}
protected List getUpdateMethodParameters(QueryLanguage ql, String update, String baseURI,
Dataset dataset, boolean includeInferred, Binding... bindings)
{
List queryParams = new ArrayList(bindings.length + 10);
queryParams.add(new BasicNameValuePair(Protocol.QUERY_LANGUAGE_PARAM_NAME, ql.getName()));
queryParams.add(new BasicNameValuePair(Protocol.UPDATE_PARAM_NAME, update));
if (baseURI != null) {
queryParams.add(new BasicNameValuePair(Protocol.BASEURI_PARAM_NAME, baseURI));
}
queryParams.add(new BasicNameValuePair(Protocol.INCLUDE_INFERRED_PARAM_NAME,
Boolean.toString(includeInferred)));
if (dataset != null) {
for (URI graphURI : dataset.getDefaultRemoveGraphs()) {
queryParams.add(new BasicNameValuePair(Protocol.REMOVE_GRAPH_PARAM_NAME, String.valueOf(graphURI)));
}
if (dataset.getDefaultInsertGraph() != null) {
queryParams.add(new BasicNameValuePair(Protocol.INSERT_GRAPH_PARAM_NAME,
String.valueOf(dataset.getDefaultInsertGraph())));
}
for (URI defaultGraphURI : dataset.getDefaultGraphs()) {
queryParams.add(new BasicNameValuePair(Protocol.USING_GRAPH_PARAM_NAME,
String.valueOf(defaultGraphURI)));
}
for (URI namedGraphURI : dataset.getNamedGraphs()) {
queryParams.add(new BasicNameValuePair(Protocol.USING_NAMED_GRAPH_PARAM_NAME,
String.valueOf(namedGraphURI)));
}
}
for (int i = 0; i < bindings.length; i++) {
String paramName = Protocol.BINDING_PREFIX + bindings[i].getName();
String paramValue = Protocol.encodeValue(bindings[i].getValue());
queryParams.add(new BasicNameValuePair(paramName, paramValue));
}
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 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 {
TupleQueryResultFormat format = TupleQueryResultFormat.matchMIMEType(mimeType, tqrFormats);
TupleQueryResultParser parser = QueryResultIO.createParser(format, getValueFactory());
BackgroundTupleResult tRes = new BackgroundTupleResult(parser, response.getEntity().getContent());
execute(tRes);
submitted = true;
return tRes;
}
catch (UnsupportedQueryResultFormatException e) {
throw new RepositoryException("Server responded with an unsupported file format: " + mimeType);
}
}
finally {
if (!submitted)
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 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 {
TupleQueryResultFormat format = TupleQueryResultFormat.matchMIMEType(mimeType, tqrFormats);
TupleQueryResultParser parser = QueryResultIO.createParser(format, getValueFactory());
parser.setQueryResultHandler(handler);
parser.parseQueryResult(response.getEntity().getContent());
}
catch (UnsupportedQueryResultFormatException e) {
throw new RepositoryException("Server responded with an unsupported file format: " + mimeType);
}
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 tqrFormats)
throws RepositoryException, IOException, QueryInterruptedException, MalformedQueryException
{
for (TupleQueryResultFormat 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;
}
method.addHeader(ACCEPT_PARAM_NAME, acceptParam);
}
}
try {
return executeOK(method);
}
catch (RepositoryException e) {
throw e;
}
catch (MalformedQueryException e) {
throw e;
}
catch (QueryInterruptedException e) {
throw e;
}
catch (OpenRDFException 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 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 {
// if we get here, HTTP code is 200
String mimeType = getResponseMIMEType(response);
try {
RDFFormat format = RDFFormat.matchMIMEType(mimeType, rdfFormats);
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();
BackgroundGraphResult gRes = new BackgroundGraphResult(parser, entity.getContent(), charset,
baseURI);
execute(gRes);
submitted = true;
return gRes;
}
catch (UnsupportedQueryResultFormatException e) {
throw new RepositoryException("Server responded with an unsupported file format: " + mimeType);
}
}
finally {
if (!submitted) {
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 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);
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 (UnsupportedRDFormatException e) {
throw new RepositoryException("Server responded with an unsupported file format: " + mimeType);
}
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 rdfFormats)
throws RepositoryException, IOException, QueryInterruptedException, MalformedQueryException
{
List acceptParams = RDFFormat.getAcceptParams(rdfFormats, requireContext,
getPreferredRDFFormat());
for (String acceptParam : acceptParams) {
method.addHeader(ACCEPT_PARAM_NAME, acceptParam);
}
try {
return executeOK(method);
}
catch (RepositoryException e) {
throw e;
}
catch (MalformedQueryException e) {
throw e;
}
catch (QueryInterruptedException e) {
throw e;
}
catch (OpenRDFException 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 OpenRDFException
*/
protected boolean getBoolean(HttpUriRequest method)
throws IOException, OpenRDFException
{
// Specify which formats we support using Accept headers
Set 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 {
BooleanQueryResultFormat format = BooleanQueryResultFormat.matchMIMEType(mimeType, booleanFormats);
BooleanQueryResultParser parser = QueryResultIO.createParser(format);
QueryResultCollector results = new QueryResultCollector();
parser.setQueryResultHandler(results);
parser.parseQueryResult(response.getEntity().getContent());
return results.getBoolean();
}
catch (UnsupportedQueryResultFormatException e) {
throw new RepositoryException("Server responded with an unsupported file format: " + mimeType);
}
catch (QueryResultParseException e) {
throw new RepositoryException("Malformed query result from server", e);
}
}
finally {
EntityUtils.consumeQuietly(response.getEntity());
}
}
private HttpResponse sendBooleanQueryViaHttp(HttpUriRequest method,
Set booleanFormats)
throws IOException, OpenRDFException
{
for (BooleanQueryResultFormat 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;
}
method.addHeader(ACCEPT_PARAM_NAME, acceptParam);
}
}
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 OpenRDFException
*/
protected HttpResponse executeOK(HttpUriRequest method)
throws IOException, OpenRDFException
{
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-Sesame 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, OpenRDFException
{
HttpResponse response = execute(method);
try {
if (response.getStatusLine().getStatusCode() >= 300) {
// trying to contact a non-Sesame server?
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, OpenRDFException
{
boolean consume = true;
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 {
throw new RepositoryException(errInfo.toString());
}
}
}
}
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
* Content-Type: application/xml;charset=UTF-8, this method will
* return application/xml as the MIME type.
*
* @param method
* The method to get the reponse MIME type from.
* @return The response MIME type, or null 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() {
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) {
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, (int)timeout);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy