You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2024/02/28 09:19:20 UTC
(jena) 01/02: GH-2233: Improved handling of specifically set HTTP headers in RDFLinkHTTP
This is an automated email from the ASF dual-hosted git repository.
andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
commit fcd6f37125b181f8b2121ec5e8f02bc028436249
Author: Claus Stadler <Ra...@googlemail.com>
AuthorDate: Sat Feb 10 02:21:02 2024 +0100
GH-2233: Improved handling of specifically set HTTP headers in RDFLinkHTTP
---
.../org/apache/jena/http/sys/ExecHTTPBuilder.java | 2 +-
.../jena/sparql/exec/http/QueryExecHTTP.java | 5 +
.../sparql/exec/http/QueryExecHTTPBuilder.java | 2 +-
.../apache/jena/test/rdflink/TestRDFLinkHTTP.java | 8 +
.../java/org/apache/jena/rdflink/RDFLinkHTTP.java | 175 ++++++++++++++-------
.../jena/rdfconnection/TestRDFConnectionHTTP.java | 97 ++++++++++++
6 files changed, 227 insertions(+), 62 deletions(-)
diff --git a/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java b/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java
index 2aadd76542..a1f1340b17 100644
--- a/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java
+++ b/jena-arq/src/main/java/org/apache/jena/http/sys/ExecHTTPBuilder.java
@@ -109,7 +109,7 @@ public abstract class ExecHTTPBuilder<X, Y> {
}
/** Set the query */
- private void setQuery(Query query, String queryStr) {
+ protected void setQuery(Query query, String queryStr) {
this.query = query;
this.queryString = queryStr;
}
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java
index 5e43d7ab64..76ebeee9ab 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTP.java
@@ -147,6 +147,11 @@ public class QueryExecHTTP implements QueryExec {
this.httpClient = HttpLib.dft(httpClient, HttpEnv.getDftHttpClient());
}
+ /** Getter for the appProvidedAcceptHeader. Only used for testing. */
+ public String getAppProvidedAcceptHeader() {
+ return appProvidedAcceptHeader;
+ }
+
/** The Content-Type response header received (null before the remote operation is attempted). */
public String getHttpResponseContentType() {
return httpResponseContentType;
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTPBuilder.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTPBuilder.java
index 2ee78a9a03..17afa987fb 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTPBuilder.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/http/QueryExecHTTPBuilder.java
@@ -36,7 +36,7 @@ public class QueryExecHTTPBuilder extends ExecHTTPBuilder<QueryExecHTTP, QueryEx
public static QueryExecHTTPBuilder service(String serviceURL) { return create().endpoint(serviceURL); }
- private QueryExecHTTPBuilder() {}
+ protected QueryExecHTTPBuilder() {}
@Override
protected QueryExecHTTPBuilder thisBuilder() {
diff --git a/jena-integration-tests/src/test/java/org/apache/jena/test/rdflink/TestRDFLinkHTTP.java b/jena-integration-tests/src/test/java/org/apache/jena/test/rdflink/TestRDFLinkHTTP.java
index 5f2aa59481..aaf51be4c3 100644
--- a/jena-integration-tests/src/test/java/org/apache/jena/test/rdflink/TestRDFLinkHTTP.java
+++ b/jena-integration-tests/src/test/java/org/apache/jena/test/rdflink/TestRDFLinkHTTP.java
@@ -109,4 +109,12 @@ public class TestRDFLinkHTTP extends AbstractTestRDFLink {
}
}
}
+
+ @Test(expected = QueryParseException.class)
+ public void non_standard_syntax_3() {
+ RDFLink link = link(true);
+ try (link) {
+ link.query("custom");
+ }
+ }
}
diff --git a/jena-rdfconnection/src/main/java/org/apache/jena/rdflink/RDFLinkHTTP.java b/jena-rdfconnection/src/main/java/org/apache/jena/rdflink/RDFLinkHTTP.java
index 392cd16011..7bc7fed84d 100644
--- a/jena-rdfconnection/src/main/java/org/apache/jena/rdflink/RDFLinkHTTP.java
+++ b/jena-rdfconnection/src/main/java/org/apache/jena/rdflink/RDFLinkHTTP.java
@@ -37,8 +37,10 @@ import org.apache.jena.sparql.engine.binding.Binding;
import org.apache.jena.sparql.exec.*;
import org.apache.jena.sparql.exec.http.DSP;
import org.apache.jena.sparql.exec.http.GSP;
+import org.apache.jena.sparql.exec.http.QueryExecHTTP;
import org.apache.jena.sparql.exec.http.QueryExecHTTPBuilder;
import org.apache.jena.sparql.exec.http.UpdateExecHTTPBuilder;
+import org.apache.jena.sparql.util.Context;
import org.apache.jena.system.Txn;
import org.apache.jena.update.UpdateFactory;
import org.apache.jena.update.UpdateRequest;
@@ -244,74 +246,127 @@ public class RDFLinkHTTP implements RDFLink {
return createQExecBuilder();
}
- // Create the QExec
-
+ /** Create the QExec initialized with a query object or string. */
private QueryExec queryExec(Query query, String queryString, QueryType queryType) {
- checkQuery();
- if ( query == null && queryString == null )
- throw new InternalErrorException("Both query and query string are null");
- if ( query == null ) {
- if ( parseCheckQueries )
- // Don't retain the query.
- QueryFactory.create(queryString);
+ QueryExecHTTPBuilderOverRDFLinkHTTP builder = createQExecBuilder();
+ if (queryString != null) {
+ builder.queryStringValidated(queryString, Syntax.defaultQuerySyntax, queryType);
}
-
- // Use the query string as provided if possible, otherwise serialize the query.
- String queryStringToSend = ( queryString != null ) ? queryString : query.toString();
- return createQExec(query, queryStringToSend, queryType);
+ if (query != null) {
+ builder.query(query);
+ }
+ return QueryExecApp.create(builder, null, query, queryString);
}
- // Create the QExec
+ /**
+ * An internal subclass of {@link QueryExecHTTPBuilder} that overrides
+ * {@link #buildX(HttpClient, Query, String, Context)}
+ * in order to have the accept header field derived based on this {@link RDFLinkHTTP} configuration.
+ */
+ /* package */ class QueryExecHTTPBuilderOverRDFLinkHTTP
+ extends QueryExecHTTPBuilder
+ {
+ /** The query type. Never null. */
+ protected QueryType queryType = QueryType.UNKNOWN;
- /** Create a builder, configured with the link setup. */
- private QueryExecHTTPBuilder createQExecBuilder() {
- return QueryExecHTTPBuilder.create().endpoint(svcQuery).httpClient(httpClient);
- }
-
- private QueryExec createQExec(Query query, String queryStringToSend, QueryType queryType) {
- QueryExecHTTPBuilder builder = createQExecBuilder().queryString(queryStringToSend);
- QueryType qt = queryType;
- if ( query != null && qt == null )
- qt = query.queryType();
- if ( qt == null )
- qt = QueryType.UNKNOWN;
- // Set the accept header - use the most specific method.
- String requestAcceptHeader = null;
- switch(qt) {
- case SELECT :
- if ( acceptSelectResult != null )
- requestAcceptHeader = acceptSelectResult;
- break;
- case ASK :
- if ( acceptAskResult != null )
- requestAcceptHeader = acceptAskResult;
- break;
- case DESCRIBE :
- case CONSTRUCT :
- if ( acceptGraph != null )
- requestAcceptHeader = acceptGraph;
- break;
- case UNKNOWN:
- // All-purpose content type.
- if ( acceptSparqlResults != null )
- requestAcceptHeader = acceptSparqlResults;
- else
- // No idea! Set an "anything" and hope.
- // (Reasonable chance this is going to end up as HTML though.)
- requestAcceptHeader = "*/*";
- default :
- break;
+ /** The parsed query is only needed for setting the appropriate HTTP header for construct quad queries. */
+ protected Query parsedQuery = null;
+
+ protected QueryExecHTTPBuilderOverRDFLinkHTTP() {
+ super();
+ }
+
+ /** This method wraps {@link #queryString(String)} and sets the additional attributes without further interpretation. */
+ protected QueryExecHTTPBuilder queryString(String queryString, QueryType queryType, Query parsedQuery) {
+ super.queryString(queryString);
+ this.queryType = queryType != null ? queryType : QueryType.UNKNOWN;
+ this.parsedQuery = parsedQuery;
+ return thisBuilder();
}
- // Make sure it was set somehow.
- if ( requestAcceptHeader == null )
- throw new JenaConnectionException("No Accept header");
- if ( requestAcceptHeader != null )
- builder.acceptHeader(requestAcceptHeader);
- // Delayed creation so QueryExecution.setTimeout works.
+ /** {@inheritDoc} */
+ @Override
+ public QueryExecHTTPBuilder queryString(String queryString) {
+ return queryString(queryString, null, null);
+ }
+
+ /** This method is far only called from RDFLinkHTTP. It validates the query string if parseCheckQueries is enabled. */
+ protected QueryExecHTTPBuilder queryStringValidated(String queryString, Syntax syntax, QueryType fallbackQueryType) {
+ Query parsedQuery = RDFLinkHTTP.this.parseCheckQueries
+ ? QueryFactory.create(queryString, syntax)
+ : null;
+
+ QueryType finalQueryType = parsedQuery != null ? parsedQuery.queryType() : fallbackQueryType;
+ return queryString(queryString, finalQueryType, parsedQuery);
+ }
- builder.queryString(queryStringToSend);
- return QueryExecApp.create(builder, null, query, queryStringToSend);
+ @Override
+ protected void setQuery(Query query, String queryStr) {
+ super.setQuery(query, queryStr);
+ parsedQuery = query;
+ queryType = query == null ? QueryType.UNKNOWN : query.queryType();
+ }
+
+ @Override
+ protected QueryExecHTTP buildX(HttpClient hClient, Query queryActual, String queryStringActual, Context cxt) {
+ // If the appAcceptHeader has not been explicitly set then derive it.
+ if (appAcceptHeader == null) {
+ String requestAcceptHeader = null;
+ // Use the most specific method.
+ switch(queryType) {
+ case SELECT :
+ if ( acceptSelectResult != null )
+ requestAcceptHeader = RDFLinkHTTP.this.acceptSelectResult;
+ break;
+ case ASK :
+ if ( acceptAskResult != null )
+ requestAcceptHeader = RDFLinkHTTP.this.acceptAskResult;
+ break;
+ case DESCRIBE :
+ case CONSTRUCT :
+ // Best effort to handle construct quads. Requires a parsed query.
+ if (parsedQuery != null && parsedQuery.isConstructQuad()) {
+ if ( acceptDataset != null )
+ requestAcceptHeader = RDFLinkHTTP.this.acceptDataset;
+ } else {
+ if ( acceptGraph != null )
+ requestAcceptHeader = RDFLinkHTTP.this.acceptGraph;
+ }
+ break;
+ case UNKNOWN:
+ // All-purpose content type.
+ if ( acceptSparqlResults != null ) {
+ requestAcceptHeader = RDFLinkHTTP.this.acceptSparqlResults;
+ } else {
+ // No idea! Set an "anything" and hope.
+ // (Reasonable chance this is going to end up as HTML though.)
+ requestAcceptHeader = "*/*";
+ }
+ break;
+ default :
+ break;
+ }
+ // Make sure it was set somehow.
+ if ( requestAcceptHeader == null )
+ throw new JenaConnectionException("No Accept header");
+
+ this.acceptHeader(requestAcceptHeader);
+ }
+
+ return super.buildX(hClient, queryActual, queryStringActual, cxt);
+ }
+ }
+
+ /**
+ * Create a builder, configured with the link setup.
+ *
+ * @param presetQueryType If non-null then the builder will force execution with this query type.
+ */
+ private QueryExecHTTPBuilderOverRDFLinkHTTP createQExecBuilder() {
+ checkQuery();
+ QueryExecHTTPBuilderOverRDFLinkHTTP builder = new QueryExecHTTPBuilderOverRDFLinkHTTP();
+ builder.endpoint(svcQuery).httpClient(httpClient);
+ return builder;
}
private void acc(StringBuilder sBuff, String acceptString) {
diff --git a/jena-rdfconnection/src/test/java/org/apache/jena/rdfconnection/TestRDFConnectionHTTP.java b/jena-rdfconnection/src/test/java/org/apache/jena/rdfconnection/TestRDFConnectionHTTP.java
new file mode 100644
index 0000000000..e17994f5dd
--- /dev/null
+++ b/jena-rdfconnection/src/test/java/org/apache/jena/rdfconnection/TestRDFConnectionHTTP.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.jena.rdfconnection;
+
+import org.apache.jena.query.Query;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.query.QueryFactory;
+import org.apache.jena.riot.WebContent;
+import org.apache.jena.sparql.exec.QueryExec;
+import org.apache.jena.sparql.exec.http.QueryExecHTTP;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestRDFConnectionHTTP {
+
+ @Test
+ public void test_select_01() {
+ doAssert(WebContent.contentTypeXML, "SELECT * { ?s a ?o } LIMIT 10");
+ }
+
+ @Test
+ public void test_ask_01() {
+ doAssert(WebContent.contentTypeJSON, "ASK { ?s a ?o }");
+ }
+
+ @Test
+ public void test_constructTriples_01() {
+ doAssert(WebContent.contentTypeTurtle, "CONSTRUCT WHERE { ?s a ?o } LIMIT 10");
+ }
+
+ @Test
+ public void test_constructQuads_01() {
+ doAssert(WebContent.contentTypeTriG, "CONSTRUCT WHERE { GRAPH ?g{ ?s a ?o } } LIMIT 10");
+ }
+
+ @Test
+ public void test_describe_01() {
+ doAssert(WebContent.contentTypeNTriples, "DESCRIBE <urn:x>");
+ }
+
+ private static RDFConnectionRemoteBuilder configureHeader(Query query, String header, RDFConnectionRemoteBuilder builder) {
+ return switch (query.queryType()) {
+ case SELECT -> builder.acceptHeaderSelectQuery(header);
+ case CONSTRUCT -> !query.isConstructQuad()
+ ? builder.acceptHeaderGraph(header)
+ : builder.acceptHeaderDataset(header);
+ case DESCRIBE -> builder.acceptHeaderGraph(header);
+ case ASK -> builder.acceptHeaderAskQuery(header);
+ default -> throw new UnsupportedOperationException("Unhandled query type for query: " + query);
+ };
+ }
+
+ /** Test whether the constructed QueryExec instance has the expected HTTP header set */
+ private static void doAssert(String expectedHeader, String queryString) {
+ Query query = QueryFactory.create(queryString);
+
+ try (RDFConnection conn = configureHeader(query, expectedHeader, RDFConnectionRemote.newBuilder())
+ .queryEndpoint("https://www.example.org/sparql") // Should never be resolved
+ .build()) {
+ try (QueryExecution qe = conn.query(queryString)) {
+ QueryExecHTTP qeh = (QueryExecHTTP)QueryExec.adapt(qe);
+ Assert.assertEquals(expectedHeader, qeh.getAppProvidedAcceptHeader());
+ }
+
+ try (QueryExecution qe = conn.newQuery().query(queryString).build()) {
+ QueryExecHTTP qeh = (QueryExecHTTP)QueryExec.adapt(qe);
+ Assert.assertEquals(expectedHeader, qeh.getAppProvidedAcceptHeader());
+ }
+
+ try (QueryExecution qe = conn.query(query)) {
+ QueryExecHTTP qeh = (QueryExecHTTP)QueryExec.adapt(qe);
+ Assert.assertEquals(expectedHeader, qeh.getAppProvidedAcceptHeader());
+ }
+
+ try (QueryExecution qe = conn.newQuery().query(query).build()) {
+ QueryExecHTTP qeh = (QueryExecHTTP)QueryExec.adapt(qe);
+ Assert.assertEquals(expectedHeader, qeh.getAppProvidedAcceptHeader());
+ }
+ }
+ }
+}