JENA-1800: Use QueryType with exec*(queryString) operations.
Use QueryType to choose the "Accept" header.
If no known query type, use an all-purpose content negotiation string.
diff --git a/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/RDFConnectionRemote.java b/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/RDFConnectionRemote.java
index fa76242..7d13541 100644
--- a/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/RDFConnectionRemote.java
+++ b/jena-rdfconnection/src/main/java/org/apache/jena/rdfconnection/RDFConnectionRemote.java
@@ -21,6 +21,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Objects;
+import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.http.HttpEntity;
@@ -78,9 +79,10 @@
protected final RDFFormat outputTriples;
protected final String acceptGraph;
protected final String acceptDataset;
- protected final String acceptSparqlResults;
+ // All purpose SPARQL results header.
protected final String acceptSelectResult;
protected final String acceptAskResult;
+ protected final String acceptSparqlResults;
// Whether to check SPARQL queries given as strings by parsing them.
protected final boolean parseCheckQueries;
@@ -142,19 +144,99 @@
return destination;
}
+ // For custom content negotiation.
+
+ // This class overrides each of these to pass down the query type as well.
+ // Then we can derive the accept header if customized without needing to parse
+ // the query. This allows an arbitrary string for a query and allows the remote
+ // server to have custom syntax extensions or interpretations of comments.
+
+ /**
+ * Execute a SELECT query and process the ResultSet with the handler code.
+ * @param queryString
+ * @param resultSetAction
+ */
+ @Override
+ public void queryResultSet(String queryString, Consumer<ResultSet> resultSetAction) {
+ Txn.executeRead(this, ()->{
+ try ( QueryExecution qExec = query(queryString, QueryType.SELECT) ) {
+ ResultSet rs = qExec.execSelect();
+ resultSetAction.accept(rs);
+ }
+ } );
+ }
+
+ /**
+ * Execute a SELECT query and process the rows of the results with the handler code.
+ * @param queryString
+ * @param rowAction
+ */
+ @Override
+ public void querySelect(String queryString, Consumer<QuerySolution> rowAction) {
+ Txn.executeRead(this, ()->{
+ try ( QueryExecution qExec = query(queryString, QueryType.SELECT) ) {
+ qExec.execSelect().forEachRemaining(rowAction);
+ }
+ } );
+ }
+
+ /** Execute a CONSTRUCT query and return as a Model */
+ @Override
+ public Model queryConstruct(String queryString) {
+ return
+ Txn.calculateRead(this, ()->{
+ try ( QueryExecution qExec = query(queryString, QueryType.CONSTRUCT) ) {
+ return qExec.execConstruct();
+ }
+ } );
+ }
+
+ /** Execute a DESCRIBE query and return as a Model */
+ @Override
+ public Model queryDescribe(String queryString) {
+ return
+ Txn.calculateRead(this, ()->{
+ try ( QueryExecution qExec = query(queryString, QueryType.DESCRIBE) ) {
+ return qExec.execDescribe();
+ }
+ } );
+ }
+
+ /** Execute a ASK query and return a boolean */
+ @Override
+ public boolean queryAsk(String queryString) {
+ return
+ Txn.calculateRead(this, ()->{
+ try ( QueryExecution qExec = query(queryString, QueryType.ASK) ) {
+ return qExec.execAsk();
+ }
+ } );
+ }
+
+ /**
+ * Operation that passed down the query type so the accept header can be set without parsing the query string.
+ * @param queryString
+ * @param queryType
+ * @return QueryExecution
+ */
+ protected QueryExecution query(String queryString, QueryType queryType) {
+ Objects.requireNonNull(queryString);
+ return queryExec(null, queryString, queryType);
+ }
+
@Override
public QueryExecution query(String queryString) {
Objects.requireNonNull(queryString);
- return queryExec(null, queryString);
+ return queryExec(null, queryString, null);
}
@Override
public QueryExecution query(Query query) {
Objects.requireNonNull(query);
- return queryExec(query, null);
+ return queryExec(query, null, null);
}
- private QueryExecution queryExec(Query query, String queryString) {
+ private QueryExecution queryExec(Query query, String queryString, QueryType queryType) {
checkQuery();
if ( query == null && queryString == null )
throw new InternalErrorException("Both query and query string are null");
@@ -164,34 +246,59 @@
}
// Use the query string as provided if possible, otherwise serialize the query.
- String queryStringToSend = ( queryString != null ) ? queryString : query.toString();
- return exec(()-> createQueryExecution(query, queryStringToSend));
+ String queryStringToSend = ( queryString != null ) ? queryString : query.toString();
+ return exec(()-> createQueryExecution(query, queryStringToSend, queryType));
}
// Create the QueryExecution
- private QueryExecution createQueryExecution(Query query, String queryStringToSend) {
+ private QueryExecution createQueryExecution(Query query, String queryStringToSend, QueryType queryType) {
QueryExecution qExec = new QueryEngineHTTP(svcQuery, queryStringToSend, httpClient, httpContext);
QueryEngineHTTP qEngine = (QueryEngineHTTP)qExec;
+ 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.
- if ( query != null ) {
- if ( query.isSelectType() && acceptSelectResult != null )
- qEngine.setAcceptHeader(acceptSelectResult);
- if ( query.isAskType() && acceptAskResult != null )
- qEngine.setAcceptHeader(acceptAskResult);
- if ( ( query.isConstructType() || query.isDescribeType() ) && acceptGraph != null )
- qEngine.setAcceptHeader(acceptGraph);
- if ( query.isConstructQuad() )
- qEngine.setDatasetContentType(acceptDataset);
+ switch(qt) {
+ case SELECT :
+ if ( acceptSelectResult != null )
+ qEngine.setAcceptHeader(acceptSelectResult);
+ break;
+ case ASK :
+ if ( acceptAskResult != null )
+ qEngine.setAcceptHeader(acceptAskResult);
+ break;
+ case DESCRIBE :
+ case CONSTRUCT :
+ if ( acceptGraph != null )
+ qEngine.setAcceptHeader(acceptGraph);
+ break;
+ case UNKNOWN:
+ // All-purpose content type.
+ if ( acceptSparqlResults != null )
+ qEngine.setAcceptHeader(acceptSparqlResults);
+ else
+ // No idea! Set an "anything" and hope.
+ // (Reasonable chance this is going to end up as HTML though.)
+ qEngine.setAcceptHeader("*/*");
+ default :
+ break;
}
- // Use the general one.
- if ( qEngine.getAcceptHeader() == null && acceptSparqlResults != null )
- qEngine.setAcceptHeader(acceptSparqlResults);
// Make sure it was set somehow.
if ( qEngine.getAcceptHeader() == null )
throw new JenaConnectionException("No Accept header");
return qExec ;
}
+ private void acc(StringBuilder sBuff, String acceptString) {
+ if ( acceptString == null )
+ return;
+ if ( sBuff.length() != 0 )
+ sBuff.append(", ");
+ sBuff.append(acceptString);
+ }
+
@Override
public void update(String updateString) {
Objects.requireNonNull(updateString);
@@ -427,7 +534,7 @@
});
}
- /** Do a PUT or POST to a dataset, sending the contents of a daatsets.
+ /** Do a PUT or POST to a dataset, sending the contents of a dataset.
* The Content-Type is {@code application/n-quads}.
* <p>
* "Replace" implies PUT, otherwise a POST is used.
@@ -497,13 +604,13 @@
/** Create an HttpEntity for the graph. */
protected HttpEntity graphToHttpEntity(Graph graph, RDFFormat syntax) {
- // Length - leaves connection reusable.
+ // Length - leaves connection reusable.
return graphToHttpEntityWithLength(graph, syntax);
}
-
- /**
+
+ /**
* Create an HttpEntity for the graph. The HTTP entity will have the length but this
- * requires serialising the graph at the point when this function is called.
+ * requires serialising the graph at the point when this function is called.
*/
private HttpEntity graphToHttpEntityWithLength(Graph graph, RDFFormat syntax) {
String ct = syntax.getLang().getContentType().getContentType();
@@ -535,10 +642,10 @@
/** Create an HttpEntity for the dataset */
protected HttpEntity datasetToHttpEntity(DatasetGraph dataset, RDFFormat syntax) {
- // Length - leaves connection reusable.
+ // Length - leaves connection reusable.
return datasetToHttpEntityWithLength(dataset, syntax);
}
-
+
private HttpEntity datasetToHttpEntityWithLength(DatasetGraph dataset, RDFFormat syntax) {
String ct = syntax.getLang().getContentType().getContentType();
ByteArrayOutputStream out = new ByteArrayOutputStream(128*1024);
diff --git a/jena-rdfconnection/src/test/java/org/apache/jena/rdfconnection/AbstractTestRDFConnection.java b/jena-rdfconnection/src/test/java/org/apache/jena/rdfconnection/AbstractTestRDFConnection.java
index 0482e9c..91971f3 100644
--- a/jena-rdfconnection/src/test/java/org/apache/jena/rdfconnection/AbstractTestRDFConnection.java
+++ b/jena-rdfconnection/src/test/java/org/apache/jena/rdfconnection/AbstractTestRDFConnection.java
@@ -23,12 +23,9 @@
import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.atlas.junit.BaseTest;
import org.apache.jena.atlas.lib.StrUtils;
-import org.apache.jena.query.Dataset;
-import org.apache.jena.query.DatasetFactory;
-import org.apache.jena.query.ReadWrite;
+import org.apache.jena.query.*;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
-import org.apache.jena.rdfconnection.RDFConnection;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.sse.SSE;
@@ -41,10 +38,10 @@
public abstract class AbstractTestRDFConnection extends BaseTest {
// Testing data.
static String DIR = "testing/RDFConnection/";
-
+
protected abstract RDFConnection connection();
// Not all connection types support abort.
- protected abstract boolean supportsAbort();
+ protected abstract boolean supportsAbort();
// ---- Data
static String dsgdata = StrUtils.strjoinNL
@@ -54,7 +51,7 @@
," (graph :g2 (:s :p :o) (:s2 :p2 :o))"
,")"
);
-
+
static String dsgdata2 = StrUtils.strjoinNL
("(dataset"
," (graph (:x :y :z))"
@@ -62,7 +59,7 @@
,")"
);
-
+
static String graph1 = StrUtils.strjoinNL
("(graph (:s :p :o) (:s1 :p1 :o))"
);
@@ -70,7 +67,7 @@
static String graph2 = StrUtils.strjoinNL
("(graph (:s :p :o) (:s2 :p2 :o))"
);
-
+
static DatasetGraph dsg = SSE.parseDatasetGraph(dsgdata);
static Dataset dataset = DatasetFactory.wrap(dsg);
static DatasetGraph dsg2 = SSE.parseDatasetGraph(dsgdata2);
@@ -91,9 +88,9 @@
// Allow multiple close()
conn.close();
}
-
+
@Test public void dataset_load_1() {
- String testDataFile = DIR+"data.trig";
+ String testDataFile = DIR+"data.trig";
try ( RDFConnection conn = connection() ) {
conn.loadDataset(testDataFile);
Dataset ds0 = RDFDataMgr.loadDataset(testDataFile);
@@ -104,7 +101,7 @@
@Test public void dataset_put_1() {
try ( RDFConnection conn = connection() ) {
- conn.putDataset(dataset);
+ conn.putDataset(dataset);
Dataset ds1 = conn.fetchDataset();
assertTrue("Datasets not isomorphic", isomorphic(dataset, ds1));
}
@@ -112,7 +109,7 @@
@Test public void dataset_put_2() {
try ( RDFConnection conn = connection() ) {
- conn.putDataset(dataset);
+ conn.putDataset(dataset);
conn.putDataset(dataset2);
Dataset ds1 = conn.fetchDataset();
assertTrue("Datasets not isomorphic", isomorphic(dataset2, ds1));
@@ -126,7 +123,7 @@
assertTrue("Datasets not isomorphic", isomorphic(dataset, ds1));
}
}
-
+
@Test public void dataset_post_2() {
try ( RDFConnection conn = connection() ) {
conn.loadDataset(dataset);
@@ -140,9 +137,9 @@
}
// Default graph
-
+
@Test public void graph_load_1() {
- String testDataFile = DIR+"data.ttl";
+ String testDataFile = DIR+"data.ttl";
Model m0 = RDFDataMgr.loadModel(testDataFile);
try ( RDFConnection conn = connection() ) {
conn.load(testDataFile);
@@ -153,7 +150,7 @@
@Test public void graph_put_1() {
try ( RDFConnection conn = connection() ) {
- conn.put(model1);
+ conn.put(model1);
Dataset ds1 = conn.fetchDataset();
Model m0 = conn.fetch();
assertTrue("Models not isomorphic", isomorphic(model1, ds1.getDefaultModel()));
@@ -164,7 +161,7 @@
@Test public void graph_put_2() {
try ( RDFConnection conn = connection() ) {
- conn.put(model1);
+ conn.put(model1);
conn.put(model2);
Model m = conn.fetch();
assertTrue("Models not isomorphic", isomorphic(m, model2));
@@ -179,7 +176,7 @@
assertTrue("Models not isomorphic", isomorphic(m, model1));
}
}
-
+
@Test public void graph_post_2() {
try ( RDFConnection conn = connection() ) {
conn.load(model1);
@@ -191,11 +188,11 @@
}
// DELETE
-
+
// Named graphs
-
+
@Test public void named_graph_load_1() {
- String testDataFile = DIR+"data.ttl";
+ String testDataFile = DIR+"data.ttl";
Model m0 = RDFDataMgr.loadModel(testDataFile);
try ( RDFConnection conn = connection() ) {
conn.load(graphName, testDataFile);
@@ -208,7 +205,7 @@
@Test public void named_graph_put_1() {
try ( RDFConnection conn = connection() ) {
- conn.put(graphName, model1);
+ conn.put(graphName, model1);
Dataset ds1 = conn.fetchDataset();
Model m0 = conn.fetch(graphName);
assertTrue("Models not isomorphic", isomorphic(model1, ds1.getNamedModel(graphName)));
@@ -219,7 +216,7 @@
@Test public void named_graph_put_2() {
try ( RDFConnection conn = connection() ) {
- conn.put(graphName, model1);
+ conn.put(graphName, model1);
conn.put(graphName, model2);
Model m = conn.fetch(graphName);
assertTrue("Models not isomorphic", isomorphic(m, model2));
@@ -229,7 +226,7 @@
@Test public void named_graph_put_2_different() {
try ( RDFConnection conn = connection() ) {
- conn.put(graphName, model1);
+ conn.put(graphName, model1);
conn.put(graphName2, model2);
Model m1 = conn.fetch(graphName);
Model m2 = conn.fetch(graphName2);
@@ -245,7 +242,7 @@
assertTrue("Models not isomorphic", isomorphic(m, model1));
}
}
-
+
@Test public void named_graph_post_2() {
try ( RDFConnection conn = connection() ) {
conn.load(graphName, model1);
@@ -257,18 +254,39 @@
}
// DELETE
-
- // Remote connections don't support transactions fully.
- //@Test public void transaction_01()
+
+ // Remote connections don't support transactions fully.
+ //@Test public void transaction_01()
private static boolean isomorphic(Dataset ds1, Dataset ds2) {
return IsoMatcher.isomorphic(ds1.asDatasetGraph(), ds2.asDatasetGraph());
}
-
+
private static boolean isomorphic(Model model1, Model model2) {
return model1.isIsomorphicWith(model2);
}
-
+
+ @Test public void query_01() {
+ try ( RDFConnection conn = connection() ) {
+ Txn.executeRead(conn, ()->{
+ try ( QueryExecution qExec = conn.query("SELECT ?x {}") ) {
+ ResultSet rs = qExec.execSelect();
+ ResultSetFormatter.consume(rs);
+ }
+ });
+ }
+ }
+
+ @Test public void query_02() {
+ try ( RDFConnection conn = connection() ) {
+ Txn.executeRead(conn, ()->{
+ try ( QueryExecution qExec = conn.query("ASK{}") ) {
+ boolean b = qExec.execAsk();
+ assertTrue(b);
+ }
+ });
+ }
+ }
@Test public void query_ask_01() {
try ( RDFConnection conn = connection() ) {
@@ -322,7 +340,7 @@
assertEquals(2, m.size());
}
}
-
+
@Test public void update_01() {
try ( RDFConnection conn = connection() ) {
conn.update("INSERT DATA { <urn:x:s> <urn:x:p> <urn:x:o>}");
@@ -352,32 +370,32 @@
}
// Not all Transactional support abort.
@Test public void transaction_commit_read_01() {
- String testDataFile = DIR+"data.trig";
+ String testDataFile = DIR+"data.trig";
try ( RDFConnection conn = connection() ) {
conn.begin(ReadWrite.WRITE);
conn.loadDataset(dataset);
conn.commit();
conn.end();
-
+
conn.begin(ReadWrite.READ);
Model m = conn.fetch();
assertTrue(isomorphic(m, dataset.getDefaultModel()));
conn.end();
}
}
-
+
// Not all RDFConnections support abort.
@Test public void transaction_abort_read02() {
Assume.assumeTrue(supportsAbort());
-
- String testDataFile = DIR+"data.trig";
+
+ String testDataFile = DIR+"data.trig";
try ( RDFConnection conn = connection() ) {
conn.begin(ReadWrite.WRITE);
conn.loadDataset(testDataFile);
conn.abort();
conn.end();
-
+
conn.begin(ReadWrite.READ);
Model m = conn.fetch();
assertTrue(m.isEmpty());