Java tutorial
/* * 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.IRI; import org.openrdf.model.ValueFactory; import org.openrdf.model.impl.SimpleValueFactory; 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<String, String> additionalHttpHeaders = Collections.emptyMap(); /*--------------* * Constructors * *--------------*/ public SparqlSession(HttpClient client, ExecutorService executor) { this.httpClient = client; this.httpContext = new BasicHttpContext(); this.executor = executor; valueFactory = SimpleValueFactory.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 <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.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<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() > 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<NameValuePair> queryParams = getUpdateMethodParameters(ql, update, baseURI, dataset, includeInferred, 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) { // TODO there is a bunch of HttpRepository specific parameters here List<NameValuePair> queryParams = new ArrayList<NameValuePair>(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 (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))); } } 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<NameValuePair> getUpdateMethodParameters(QueryLanguage ql, String update, String baseURI, Dataset dataset, boolean includeInferred, Binding... bindings) { List<NameValuePair> queryParams = new ArrayList<NameValuePair>(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 (IRI 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 (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))); } } 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<TupleQueryResultFormat> 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<TupleQueryResultFormat> 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<TupleQueryResultFormat> 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<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 { // 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<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); 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<RDFFormat> rdfFormats) throws RepositoryException, IOException, QueryInterruptedException, MalformedQueryException { List<String> 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<BooleanQueryResultFormat> 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<BooleanQueryResultFormat> 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 * <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() { 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); } }