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 8cc133e8e50459ef076ca54ecc81570bca3a44d5 Author: Andy Seaborne <[email protected]> AuthorDate: Sat Nov 9 15:55:50 2024 +0000 JENA-2837: Reduce use of model APIs in response handling --- .../apache/jena/sparql/exec/QueryExecResult.java | 4 +- .../apache/jena/sparql/resultset/SPARQLResult.java | 10 + .../access/AccessCtl_SPARQL_QueryDataset.java | 10 +- .../apache/jena/fuseki/access/SecurityContext.java | 14 +- .../fuseki/access/SecurityContextAllowAll.java | 9 +- .../access/SecurityContextAllowNamedGraphs.java | 10 +- .../fuseki/access/SecurityContextAllowNone.java | 9 +- .../jena/fuseki/access/SecurityContextView.java | 16 +- .../fuseki/access/TestSecurityFilterLocal.java | 52 +- .../jena/fuseki/server/ActionServiceFactory.java | 43 -- .../jena/fuseki/server/DataServiceStatus.java | 15 - .../org/apache/jena/fuseki/server/FusekiVocab.java | 21 +- .../jena/fuseki/servlets/ResponseDataset.java | 109 +---- .../apache/jena/fuseki/servlets/ResponseJson.java | 99 +--- .../apache/jena/fuseki/servlets/ResponseOps.java | 107 ----- .../jena/fuseki/servlets/ResponseResultSet.java | 234 +-------- .../org/apache/jena/fuseki/servlets/Responses.java | 526 +++++++++++++++++++++ .../jena/fuseki/servlets/SPARQLQueryProcessor.java | 58 +-- .../apache/jena/fuseki/system/GraphLoadUtils.java | 27 +- 19 files changed, 662 insertions(+), 711 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecResult.java b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecResult.java index 6185a40569..ed922e63e0 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecResult.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/exec/QueryExecResult.java @@ -29,7 +29,7 @@ import org.apache.jena.sparql.resultset.SPARQLResult; /** * This class is for the outcome of {@link QueryExec}. * <p> - * See {@link SPARQLResult} for The Model-level equivalent. + * See {@link SPARQLResult} for the model-level equivalent. */ public class QueryExecResult { @@ -58,7 +58,7 @@ public class QueryExecResult { protected QueryExecResult() {} - public QueryExecResult(Graph model) { + public QueryExecResult(Graph graph) { set(graph); } diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/SPARQLResult.java b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/SPARQLResult.java index 692b657d7f..12d0b4510b 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/resultset/SPARQLResult.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/resultset/SPARQLResult.java @@ -22,11 +22,13 @@ import java.util.Iterator; import java.util.Objects; import org.apache.jena.atlas.json.JsonObject; +import org.apache.jena.graph.Graph; import org.apache.jena.query.Dataset; import org.apache.jena.query.DatasetFactory; import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.exec.QueryExecResult; /** @@ -132,6 +134,14 @@ public class SPARQLResult { return booleanResult; } + public Graph getGraph() { + return getModel().getGraph(); + } + + public DatasetGraph getDatasetGraph() { + return dataset.asDatasetGraph(); + } + public Model getModel() { if ( !hasBeenSet ) throw new ResultSetException("Not set"); diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java index 19d54e7b78..f4c452bc97 100644 --- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java +++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AccessCtl_SPARQL_QueryDataset.java @@ -28,9 +28,9 @@ import java.util.function.Function; import org.apache.jena.atlas.lib.Pair; import org.apache.jena.fuseki.servlets.*; import org.apache.jena.query.Query; -import org.apache.jena.query.QueryExecution; import org.apache.jena.sparql.core.*; import org.apache.jena.sparql.core.DynamicDatasets.DynamicDatasetGraph; +import org.apache.jena.sparql.exec.QueryExec; /** A Query {@link ActionService} that inserts a security filter on each query. */ final @@ -105,7 +105,7 @@ public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset { } @Override - protected QueryExecution createQueryExecution(HttpAction action, Query query, DatasetGraph target) { + protected QueryExec createQueryExec(HttpAction action, Query query, DatasetGraph target) { if ( ! ALLOW_FROM ) { if ( target instanceof DynamicDatasetGraph ) // Protocol query/FROM should have been caught by decideDataset @@ -116,13 +116,13 @@ public class AccessCtl_SPARQL_QueryDataset extends SPARQL_QueryDataset { // Dataset of the service, not computed by decideDataset. DatasetGraph dsg = action.getActiveDSG(); if ( dsg == null ) - return super.createQueryExecution(action, query, target); + return super.createQueryExec(action, query, target); if ( ! DataAccessCtl.isAccessControlled(dsg) ) - return super.createQueryExecution(action, query, target); + return super.createQueryExec(action, query, target); SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dsg, requestUser); // A QueryExecution for controlled access - QueryExecution qExec = sCxt.createQueryExecution(query, target); + QueryExec qExec = sCxt.createQueryExec(query, target); return qExec; } } diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java index b9ce9bdefb..42e31532f5 100644 --- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java +++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java @@ -31,6 +31,8 @@ import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryFactory; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.exec.QueryExec; +import org.apache.jena.sparql.exec.QueryExecutionAdapter; import org.apache.jena.sparql.util.Context; /** A {@link SecurityContext} is the things actor (user, role) is allowed to do. @@ -78,11 +80,19 @@ public interface SecurityContext { public boolean visableDefaultGraph(); + /** @deprecated Use {@link #createQueryExec(Query, DatasetGraph)} */ + @Deprecated(forRemoval = true) public default QueryExecution createQueryExecution(String queryString, DatasetGraph dsg) { return createQueryExecution(QueryFactory.create(queryString), dsg); } - public QueryExecution createQueryExecution(Query query, DatasetGraph dsg); + /** @deprecated Use {@link #createQueryExec(Query, DatasetGraph)} */ + @Deprecated(forRemoval = true) + public default QueryExecution createQueryExecution(Query query, DatasetGraph dsg) { + return QueryExecutionAdapter.adapt(createQueryExec(query, dsg)); + } + + public QueryExec createQueryExec(Query query, DatasetGraph dsg); /** * Quad filter to reflect the security policy of this {@link SecurityContext}. It is @@ -97,7 +107,7 @@ public interface SecurityContext { * Throws {@link IllegalArgumentException} if {@link DatasetGraph} is not a TDB1 or TDB2 backed dataset. * May throw {@link UnsupportedOperationException}. */ - public default void filterTDB(DatasetGraph dsg, QueryExecution qExec) { + public default void filterTDB(DatasetGraph dsg, QueryExec qExec) { if ( ! org.apache.jena.tdb1.sys.TDBInternal.isTDB1(dsg) || ! org.apache.jena.tdb2.sys.TDBInternal.isTDB2(dsg) ) throw new IllegalArgumentException("Not a TDB database"); throw new UnsupportedOperationException(); diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java index 125b984f3a..80b7b959de 100644 --- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java +++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowAll.java @@ -23,10 +23,9 @@ import java.util.function.Predicate; import org.apache.jena.graph.Node; import org.apache.jena.query.Query; -import org.apache.jena.query.QueryExecution; -import org.apache.jena.query.QueryExecutionFactory; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.exec.QueryExec; /** A {@link SecurityContext} that allow any graph, default or named. */ public class SecurityContextAllowAll implements SecurityContext { @@ -43,8 +42,8 @@ public class SecurityContextAllowAll implements SecurityContext { public boolean visableDefaultGraph() { return true; } @Override - public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) { - return QueryExecutionFactory.create(query, dsg); + public QueryExec createQueryExec(Query query, DatasetGraph dsg) { + return QueryExec.dataset(dsg).query(query).build(); } /** @@ -56,7 +55,7 @@ public class SecurityContextAllowAll implements SecurityContext { public Predicate<Quad> predicateQuad() { return q->true; } @Override - public void filterTDB(DatasetGraph dsg, QueryExecution qExec) { + public void filterTDB(DatasetGraph dsg, QueryExec qExec) { // No filter necessary. // Predicate<?> pred = tuple->true; // qExec.getContext().set(GraphFilter.getContextKey(dsg), pred); diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java index 33c64e1013..c54006fb34 100644 --- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java +++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNamedGraphs.java @@ -23,10 +23,9 @@ import java.util.function.Predicate; import org.apache.jena.graph.Node; import org.apache.jena.query.Query; -import org.apache.jena.query.QueryExecution; -import org.apache.jena.query.QueryExecutionFactory; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.exec.QueryExec; /** A {@link SecurityContext} that allows access to the default graph but not named graphs. */ public class SecurityContextAllowNamedGraphs implements SecurityContext { @@ -43,9 +42,8 @@ public class SecurityContextAllowNamedGraphs implements SecurityContext { public boolean visableDefaultGraph() { return true; } @Override - public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) { - - return QueryExecutionFactory.create(query, dsg); + public QueryExec createQueryExec(Query query, DatasetGraph dsg) { + return QueryExec.dataset(dsg).query(query).build(); } /** @@ -54,5 +52,5 @@ public class SecurityContextAllowNamedGraphs implements SecurityContext { * efficient. */ @Override - public Predicate<Quad> predicateQuad() { return q-> ! Quad.isDefaultGraph(q.getGraph()); } + public Predicate<Quad> predicateQuad() { return q -> ! Quad.isDefaultGraph(q.getGraph()); } } diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java index dcf8da9f48..d9512f49f3 100644 --- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java +++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextAllowNone.java @@ -24,11 +24,10 @@ import java.util.function.Predicate; import org.apache.jena.graph.Node; import org.apache.jena.query.Query; -import org.apache.jena.query.QueryExecution; -import org.apache.jena.query.QueryExecutionFactory; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.DatasetGraphSink; import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.exec.QueryExec; /** A {@link SecurityContext} that does not allow any access. */ public class SecurityContextAllowNone implements SecurityContext { @@ -44,15 +43,15 @@ public class SecurityContextAllowNone implements SecurityContext { public boolean visableDefaultGraph() { return false; } @Override - public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) { - return QueryExecutionFactory.create(query, DatasetGraphSink.create()); + public QueryExec createQueryExec(Query query, DatasetGraph dsg) { + return QueryExec.dataset(DatasetGraphSink.create()).query(query).build(); } @Override public Predicate<Quad> predicateQuad() { return q -> false; } @Override - public void filterTDB(DatasetGraph dsg, QueryExecution qExec) { + public void filterTDB(DatasetGraph dsg, QueryExec qExec) { Predicate<?> pred = tuple->false; qExec.getContext().set(GraphFilter.getContextKey(dsg), pred); } diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java index 10be3e25f2..a0826485ec 100644 --- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java +++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContextView.java @@ -27,10 +27,9 @@ import java.util.function.Predicate; import org.apache.jena.graph.Node; import org.apache.jena.query.Query; import org.apache.jena.query.QueryExecution; -import org.apache.jena.query.QueryExecutionFactory; -import org.apache.jena.query.QueryFactory; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.exec.QueryExec; import org.apache.jena.sparql.util.Context; import org.apache.jena.sparql.util.NodeUtils; import org.apache.jena.tdb1.TDB1Factory; @@ -87,20 +86,15 @@ public class SecurityContextView implements SecurityContext { } @Override - public QueryExecution createQueryExecution(String queryString, DatasetGraph dsg) { - return createQueryExecution(QueryFactory.create(queryString), dsg); - } - - @Override - public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) { + public QueryExec createQueryExec(Query query, DatasetGraph dsg) { if ( isAccessControlledTDB(dsg) ) { - QueryExecution qExec = QueryExecutionFactory.create(query, dsg); + QueryExec qExec = QueryExec.dataset(dsg).query(query).build(); filterTDB(dsg, qExec); return qExec; } // Not TDB - do by selecting graphs. DatasetGraph dsgA = DataAccessCtl.filteredDataset(dsg, this); - return QueryExecutionFactory.create(query, dsgA); + return QueryExec.dataset(dsgA).query(query).build(); } /** @@ -108,7 +102,7 @@ public class SecurityContextView implements SecurityContext { * {@link QueryExecution}. This does not modify the {@link DatasetGraph}. */ @Override - public void filterTDB(DatasetGraph dsg, QueryExecution qExec) { + public void filterTDB(DatasetGraph dsg, QueryExec qExec) { GraphFilter<?> predicate = predicate(dsg); qExec.getContext().set(predicate.getContextKey(), predicate); } diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java index 03630f40d0..af42f6a5f4 100644 --- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java +++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterLocal.java @@ -25,35 +25,30 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Stream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + import org.apache.jena.atlas.iterator.Iter; import org.apache.jena.atlas.lib.Creator; import org.apache.jena.atlas.lib.SetUtils; import org.apache.jena.atlas.lib.StrUtils; import org.apache.jena.graph.Graph; import org.apache.jena.graph.Node; -import org.apache.jena.query.Dataset; -import org.apache.jena.query.DatasetFactory; -import org.apache.jena.query.QueryExecution; -import org.apache.jena.query.QueryExecutionFactory; -import org.apache.jena.query.QuerySolution; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFParser; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.DatasetGraphFactory; import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.engine.binding.Binding; +import org.apache.jena.sparql.exec.QueryExec; import org.apache.jena.sparql.sse.SSE; import org.apache.jena.system.Txn; import org.apache.jena.tdb1.TDB1; import org.apache.jena.tdb1.TDB1Factory; import org.apache.jena.tdb2.DatabaseMgr; import org.apache.jena.tdb2.TDB2; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; /** Test a controlled Dataset with access by TDB filter or general DatasetGraphFiltered. */ @RunWith(Parameterized.class) @@ -117,26 +112,19 @@ public class TestSecurityFilterLocal { final DatasetGraph dsg1 = applyFilterDSG ? DataAccessCtl.filteredDataset(dsg, sCxt) : dsg; - Dataset ds = DatasetFactory.wrap(dsg1); return - Txn.calculateRead(ds, ()->{ - try(QueryExecution qExec = QueryExecutionFactory.create(queryString, ds)) { -// if ( applyFilterTDB && ! sCxt.equals(SecurityContext.NONE)) { -// ((SecurityContextView)sCxt).filterTDB(dsg1, qExec); -// } + Txn.calculateRead(dsg1, ()->{ + try(QueryExec qExec = QueryExec.dataset(dsg1).query(queryString).build()) { if ( applyFilterTDB ) sCxt.filterTDB(dsg1, qExec); - List<QuerySolution> results = Iter.toList(qExec.execSelect()); - Stream<Node> stream = results.stream() - .map(qs->qs.get("s")) - .filter(Objects::nonNull) - .map(RDFNode::asNode); + List<Binding> results = Iter.toList(qExec.select()); + Stream<Node> stream = results.stream().map(qs -> qs.get("s")).filter(Objects::nonNull); return SetUtils.toSet(stream); } }); } - private Set<Node> subjects(DatasetGraph dsg, Function<DatasetGraph, Graph> graphChoice, String queryString, SecurityContext sCxt) { + private Set<Node> subjects(DatasetGraph dsg, Function<DatasetGraph, Graph> graphChoice, String queryString, SecurityContext sCxt) { final DatasetGraph dsg1 = applyFilterDSG ? DataAccessCtl.filteredDataset(dsg, sCxt) : dsg; @@ -144,14 +132,13 @@ public class TestSecurityFilterLocal { if ( graph == null ) // Can't see the graph. return Collections.emptySet(); - Model model = ModelFactory.createModelForGraph(graph); return Txn.calculateRead(testdsg, ()->{ - try(QueryExecution qExec = QueryExecutionFactory.create(queryString, model)) { + try(QueryExec qExec = QueryExec.graph(graph).query(queryString).build()) { if ( applyFilterTDB ) sCxt.filterTDB(dsg1, qExec); - List<QuerySolution> results = Iter.toList(qExec.execSelect()); - Stream<Node> stream = results.stream().map(qs->qs.get("s")).filter(Objects::nonNull).map(RDFNode::asNode); + List<Binding> results = Iter.toList(qExec.select()); + Stream<Node> stream = results.stream().map(qs->qs.get("s")).filter(Objects::nonNull); return SetUtils.toSet(stream); } }); @@ -162,14 +149,13 @@ public class TestSecurityFilterLocal { final DatasetGraph dsg1 = applyFilterDSG ? DataAccessCtl.filteredDataset(dsg, sCxt) : dsg; - Dataset ds = DatasetFactory.wrap(dsg1); return - Txn.calculateRead(ds, ()->{ - try(QueryExecution qExec = QueryExecutionFactory.create(queryGraphNames, ds)) { + Txn.calculateRead(dsg1, ()->{ + try(QueryExec qExec = QueryExec.dataset(dsg1).query(queryGraphNames).build()) { if ( applyFilterTDB ) sCxt.filterTDB(dsg1, qExec); - List<QuerySolution> results = Iter.toList(qExec.execSelect()); - Stream<Node> stream = results.stream().map(qs->qs.get("g")).filter(Objects::nonNull).map(RDFNode::asNode); + List<Binding> results = Iter.toList(qExec.select()); + Stream<Node> stream = results.stream().map(qs->qs.get("g")).filter(Objects::nonNull); return SetUtils.toSet(stream); } }); diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ActionServiceFactory.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ActionServiceFactory.java deleted file mode 100644 index bf396db2f2..0000000000 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ActionServiceFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.fuseki.server; - -import org.apache.jena.fuseki.servlets.ActionProcessor; -import org.apache.jena.fuseki.servlets.ActionService; -import org.apache.jena.rdf.model.Resource; - -/** - * Factory for creating the {@link ActionProcessor} for an endpoint. Each endpoint - - * a service name for a dataset has an associated {@link ActionProcessor} to handle - * the request. This created when the configuration file is read or the server built - * programmatically. - * - * {@link ActionService} is a common super class of request handlers, includes counters - * for operations and has a validate-execute lifecycle. - * - * @see ActionService - */ -@FunctionalInterface -public interface ActionServiceFactory { - /** - * Create an {@link ActionServiceFactory} (it can be shared with endpoints), given the description - * which is a link into the server configuration graph. - */ - public ActionService newActionService(Operation operation, Resource endpoint); -} diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java index 20047616fa..43706685f5 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataServiceStatus.java @@ -18,8 +18,6 @@ package org.apache.jena.fuseki.server; -import org.apache.jena.rdf.model.Resource; - public enum DataServiceStatus { UNINITIALIZED("Uninitialized"), @@ -30,17 +28,4 @@ public enum DataServiceStatus { public final String name; DataServiceStatus(String string) { name = string; } - - public static DataServiceStatus status(Resource r) { - if ( FusekiVocab.stateActive.equals(r) ) - return ACTIVE; - if ( FusekiVocab.stateOffline.equals(r) ) - return OFFLINE; - if ( FusekiVocab.stateClosing.equals(r) ) - return CLOSING; - if ( FusekiVocab.stateClosed.equals(r) ) - return CLOSED; - return null; - } } - diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java index 8efe594790..47974435d9 100755 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java @@ -21,15 +21,13 @@ package org.apache.jena.fuseki.server; import org.apache.jena.fuseki.FusekiException; import org.apache.jena.irix.IRIException; import org.apache.jena.irix.IRIx; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; public class FusekiVocab { public static String NS = "http://jena.apache.org/fuseki#"; - private static Model model = ModelFactory.createDefaultModel(); public static final Resource tServer = resource("Server"); @@ -92,21 +90,8 @@ public class FusekiVocab public static final Resource opPREFIXES_R = resource("prefixes-r"); public static final Resource opPREFIXES_RW = resource("prefixes-rw"); - // Internal - private static final String stateNameActive = DataServiceStatus.ACTIVE.name; - private static final String stateNameOffline = DataServiceStatus.OFFLINE.name; - private static final String stateNameClosing = DataServiceStatus.CLOSING.name; - private static final String stateNameClosed = DataServiceStatus.CLOSED.name; - - public static final Resource stateActive = resource(stateNameActive); - public static final Resource stateOffline = resource(stateNameOffline); - public static final Resource stateClosing = resource(stateNameClosing); - public static final Resource stateClosed = resource(stateNameClosed); - -// public static final Property pStatus = property("status"); - - private static Resource resource(String localname) { return model.createResource(iri(localname)); } - private static Property property(String localname) { return model.createProperty(iri(localname)); } + private static Resource resource(String localname) { return ResourceFactory.createResource(iri(localname)); } + private static Property property(String localname) { return ResourceFactory.createProperty(iri(localname)); } private static String iri(String localname) { String uri = NS + localname; diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java index 43eafc7ea6..5d9635ffc3 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java @@ -18,121 +18,24 @@ package org.apache.jena.fuseki.servlets; -import static org.apache.jena.riot.WebContent.*; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletRequest; - -import org.apache.jena.atlas.web.MediaType; -import org.apache.jena.fuseki.DEF; -import org.apache.jena.fuseki.Fuseki; -import org.apache.jena.fuseki.system.ConNeg; -import org.apache.jena.fuseki.system.FusekiNetLib; import org.apache.jena.query.Dataset; import org.apache.jena.query.DatasetFactory; import org.apache.jena.rdf.model.Model; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFFormat; -import org.apache.jena.riot.RDFLanguages; -import org.apache.jena.shared.JenaException; -import org.apache.jena.web.HttpSC; +import org.apache.jena.sparql.exec.QueryExec; +/** + * @deprecated Use {@link QueryExec} and {@link Responses}. + */ +@Deprecated(forRemoval = true) public class ResponseDataset { - // Short names for "output=" - private static final String contentOutputJSONLD = "json-ld"; - private static final String contentOutputJSONRDF = "json-rdf"; - private static final String contentOutputJSON = "json"; - private static final String contentOutputXML = "xml"; - private static final String contentOutputText = "text"; - private static final String contentOutputTTL = "ttl"; - private static final String contentOutputTurtle = "turtle"; - private static final String contentOutputNT = "nt"; - private static final String contentOutputTriG = "trig"; - private static final String contentOutputNQuads = "n-quads"; - - public static Map<String,String> shortNamesModel = new HashMap<>(); - static { - // Some short names. keys are lowercase. - ResponseOps.put(shortNamesModel, contentOutputJSONLD, contentTypeJSONLD); - ResponseOps.put(shortNamesModel, contentOutputJSONRDF, contentTypeRDFJSON); - ResponseOps.put(shortNamesModel, contentOutputJSON, contentTypeJSONLD); - ResponseOps.put(shortNamesModel, contentOutputXML, contentTypeRDFXML); - ResponseOps.put(shortNamesModel, contentOutputText, contentTypeTurtle); - ResponseOps.put(shortNamesModel, contentOutputTTL, contentTypeTurtle); - ResponseOps.put(shortNamesModel, contentOutputTurtle, contentTypeTurtle); - ResponseOps.put(shortNamesModel, contentOutputNT, contentTypeNTriples); - ResponseOps.put(shortNamesModel, contentOutputNQuads, contentTypeNQuads); - ResponseOps.put(shortNamesModel, contentOutputTriG, contentTypeTriG); - } - public static void doResponseModel(HttpAction action, Model model) { Dataset ds = DatasetFactory.wrap(model); ResponseDataset.doResponseDataset(action, ds); } public static void doResponseDataset(HttpAction action, Dataset dataset) { - HttpServletRequest request = action.getRequest(); - - String mimeType = null; // Header request type - - MediaType i = ConNeg.chooseContentType(request, DEF.constructOffer, DEF.acceptTurtle); - if ( i != null ) - mimeType = i.getContentTypeStr(); - - String outputField = ResponseOps.paramOutput(request, shortNamesModel); - if ( outputField != null ) - mimeType = outputField; - - String writerMimeType = mimeType; - - if ( mimeType == null ) { - Fuseki.actionLog.warn("Can't find MIME type for response"); - String x = FusekiNetLib.getAccept(request); - String msg; - if ( x == null ) - msg = "No Accept: header"; - else - msg = "Accept: " + x + " : Not understood"; - ServletOps.error(HttpSC.NOT_ACCEPTABLE_406, msg); - } - - String contentType = mimeType; - String charset = charsetUTF8; - - String forceAccept = ResponseOps.paramForceAccept(request); - if ( forceAccept != null ) { - contentType = forceAccept; - charset = charsetUTF8; - } - - Lang lang = RDFLanguages.contentTypeToLang(contentType); - if ( lang == null ) - ServletOps.errorBadRequest("Can't determine output content type: "+contentType); - RDFFormat format = ActionLib.getNetworkFormatForLang(lang); - - try { - ServletOps.success(action); - ServletOutputStream out = action.getResponseOutputStream(); - try { - // Use the Content-Type from the content negotiation. - if ( RDFLanguages.isQuads(lang) ) - ActionLib.datasetResponse(action, dataset.asDatasetGraph(), format, contentType); - else - ActionLib.graphResponse(action, dataset.getDefaultModel().getGraph(), format, contentType); - out.flush(); - } catch (JenaException ex) { - ServletOps.errorOccurred("Failed to write output: "+ex.getMessage(), ex); - } - } - catch (ActionErrorException ex) { throw ex; } - catch (Exception ex) { - action.log.info("Exception while writing the response model: "+ex.getMessage(), ex); - ServletOps.errorOccurred("Exception while writing the response model: "+ex.getMessage(), ex); - } + Responses.doResponseDataset(action, dataset.asDatasetGraph()); } } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseJson.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseJson.java index 9f02811f46..8c7ce104ed 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseJson.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseJson.java @@ -18,34 +18,17 @@ package org.apache.jena.fuseki.servlets; -import static java.lang.String.format; - -import java.io.IOException; -import java.io.PrintStream; import java.util.Iterator; -import jakarta.servlet.ServletOutputStream; - -import org.apache.commons.lang3.StringUtils; import org.apache.jena.atlas.json.JsonObject; -import org.apache.jena.atlas.lib.StrUtils; -import org.apache.jena.fuseki.FusekiException; -import org.apache.jena.fuseki.servlets.ResponseResultSet.OutputContent; -import org.apache.jena.query.QueryCancelledException; -import org.apache.jena.query.ResultSetFormatter; -import org.apache.jena.riot.WebContent; -import org.apache.jena.web.HttpSC; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.jena.sparql.exec.QueryExec; /** - * Responsible for handling JSON response output. + * @deprecated Use {@link QueryExec} and {@link Responses}. */ +@Deprecated(forRemoval = true) public class ResponseJson { - // Loggers - private static Logger xlog = LoggerFactory.getLogger(ResponseJson.class); - /** * Outputs a JSON query result * @@ -53,80 +36,6 @@ public class ResponseJson { * @param jsonItem a ResultSetJsonStream instance */ public static void doResponseJson(HttpAction action, Iterator<JsonObject> jsonItem) { - if ( jsonItem == null ) { - xlog.warn("doResponseJson: Result set is null"); - throw new FusekiException("Result set is null"); - } - - jsonOutput(action, jsonItem); - } - - private static void jsonOutput(HttpAction action, final Iterator<JsonObject> jsonItems) { - OutputContent proc = out-> { - if ( jsonItems != null ) - ResultSetFormatter.output(out, jsonItems); - }; - - try { - String callback = ResponseOps.paramCallback(action.getRequest()); - ServletOutputStream out = action.getResponseOutputStream(); - - if ( callback != null ) { - callback = StringUtils.replaceChars(callback, "\r", ""); - callback = StringUtils.replaceChars(callback, "\n", ""); - out.write(StrUtils.asUTF8bytes(callback)); - out.write('('); out.write('\n'); - } - - output(action, "application/json", WebContent.charsetUTF8, proc); - - if ( callback != null ) { - out.write(')'); out.write('\n'); - } - } catch (IOException ex) { - ServletOps.errorOccurred(ex); - } - } - - private static void output(HttpAction action, String contentType, String charset, OutputContent proc) { - try { - setHttpResponse(action, contentType, charset); - action.setResponseStatus(HttpSC.OK_200); - ServletOutputStream out = action.getResponseOutputStream(); - try { - proc.output(out); - out.flush(); - } catch (QueryCancelledException ex) { - // Bother. Status code 200 already sent. - xlog.info(format("[%d] Query Cancelled - results truncated (but 200 already sent)", action.id)); - PrintStream ps = new PrintStream(out); - ps.println(); - ps.println("## Query cancelled due to timeout during execution ##"); - ps.println("## **** Incomplete results **** ##"); - ps.flush(); - out.flush(); - // No point raising an exception - 200 was sent already. - // errorOccurred(ex); - } - // Includes client gone. - } catch (IOException ex) { - ServletOps.errorOccurred(ex); - } - // Do not call httpResponse.flushBuffer(); here - Jetty closes the stream if - // it is a gzip stream - // then the JSON callback closing details can't be added. - } - - public static void setHttpResponse(HttpAction action, String contentType, String charset) { - // ---- Set up HTTP Response - // Stop caching (not that ?queryString URLs are cached anyway) - ServletOps.setNoCache(action); - // See: http://www.w3.org/International/O-HTTP-charset.html - if ( contentType != null ) { - if ( charset != null ) - contentType = contentType + "; charset=" + charset; - xlog.trace("Content-Type for response: " + contentType); - action.setResponseContentType(contentType); - } + Responses.doResponseJson(action, jsonItem); } } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseOps.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseOps.java deleted file mode 100644 index d98afc439a..0000000000 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseOps.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.fuseki.servlets; - -import static org.apache.jena.atlas.lib.Lib.lowercase; - -import java.io.IOException; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; -import org.apache.jena.riot.WebContent; -import org.apache.jena.riot.web.HttpNames; - -class ResponseOps { - // Helpers - public static void put(Map<String, String> map, String key, String value) { - map.put(lowercase(key), value); - } - - public static boolean isEOFexception(IOException ioEx) { - if ( ioEx.getClass().getName().equals("org.mortbay.jetty.EofException eofEx") ) - return true; - if ( ioEx instanceof java.io.EOFException ) - return true; - return false; - } - - public static String paramForceAccept(HttpServletRequest request) { - String x = fetchParam(request, HttpNames.paramForceAccept); - return x; - } - - public static String paramStylesheet(HttpServletRequest request) { - return fetchParam(request, HttpNames.paramStyleSheet); - } - - public static String paramOutput(HttpServletRequest request, Map<String, String> map) { - // Two names. - String x = fetchParam(request, HttpNames.paramOutput1); - if ( x == null ) - x = fetchParam(request, HttpNames.paramOutput2); - if ( x == null ) - x = fetchParam(request, HttpNames.paramOutput3); - return expandShortName(x, map); - } - - public static String expandShortName(String str, Map<String, String> map) { - if ( str == null ) - return null; - // Force keys to lower case. See put() above. - String key = lowercase(str); - String str2 = map.get(key); - if ( str2 == null ) - return str; - return str2; - } - - public static String paramCallback(HttpServletRequest request) { - return fetchParam(request, HttpNames.paramCallback); - } - - public static String fetchParam(HttpServletRequest request, String parameterName) { - String value = request.getParameter(parameterName); - if ( value != null ) { - value = value.trim(); - if ( value.length() == 0 ) - value = null; - } - return value; - } - - /** Basic settings, including Content-Type, for a response. */ - public static void setHttpResponse(HttpAction action, String contentType, String charset) { - // ---- Set up HTTP Response - // Stop caching (not that ?queryString URLs are cached anyway) - if ( true ) - ServletOps.setNoCache(action); - // See: http://www.w3.org/International/O-HTTP-charset.html - if ( contentType != null ) { - if ( charset != null && !isXML(contentType) ) - contentType = contentType + "; charset=" + charset; - action.log.trace("Content-Type for response: " + contentType); - action.setResponseContentType(contentType); - } - } - - public static boolean isXML(String contentType) { - return contentType.equals(WebContent.contentTypeRDFXML) || contentType.equals(WebContent.contentTypeResultsXML) - || contentType.equals(WebContent.contentTypeXML); - } -} diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java index c20e35bcc1..51a51c4dfa 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseResultSet.java @@ -18,238 +18,24 @@ package org.apache.jena.fuseki.servlets; -import static java.lang.String.format; -import static org.apache.jena.riot.WebContent.*; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import jakarta.servlet.http.HttpServletRequest; - -import org.apache.jena.atlas.lib.StrUtils; -import org.apache.jena.atlas.web.AcceptList; -import org.apache.jena.atlas.web.MediaType; -import org.apache.jena.fuseki.DEF; -import org.apache.jena.fuseki.FusekiException; -import org.apache.jena.fuseki.system.ConNeg; -import org.apache.jena.query.QueryCancelledException; import org.apache.jena.query.ResultSet; -import org.apache.jena.query.ResultSetFormatter; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.WebContent; -import org.apache.jena.riot.resultset.ResultSetWriterRegistry; -import org.apache.jena.riot.rowset.rw.RowSetWriterXML; import org.apache.jena.sparql.core.Prologue; -import org.apache.jena.sparql.resultset.ResultsWriter; -import org.apache.jena.sparql.util.Context; -import org.apache.jena.web.HttpSC; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.jena.sparql.exec.QueryExec; +import org.apache.jena.sparql.exec.RowSet; -/** This is the content negotiation for each kind of SPARQL query result */ +/** + * This is the content negotiation for each kind of SPARQL query result. + * @deprecated Use {@link QueryExec} and {@link Responses}. + */ +@Deprecated(forRemoval = true) public class ResponseResultSet { - private static Logger xlog = LoggerFactory.getLogger(ResponseResultSet.class); - - // Short names for "output=" - private static final String contentOutputJSON = "json"; - private static final String contentOutputXML = "xml"; - private static final String contentOutputSPARQL = "sparql"; - private static final String contentOutputText = "text"; - private static final String contentOutputCSV = "csv"; - private static final String contentOutputTSV = "tsv"; - private static final String contentOutputThrift = "thrift"; - - public static Map<String,String> shortNamesResultSet = new HashMap<>(); - static { - // Some short names. keys are lowercase. - ResponseOps.put(shortNamesResultSet, contentOutputJSON, contentTypeResultsJSON); - ResponseOps.put(shortNamesResultSet, contentOutputSPARQL, contentTypeResultsXML); - ResponseOps.put(shortNamesResultSet, contentOutputXML, contentTypeResultsXML); - ResponseOps.put(shortNamesResultSet, contentOutputText, contentTypeTextPlain); - ResponseOps.put(shortNamesResultSet, contentOutputCSV, contentTypeTextCSV); - ResponseOps.put(shortNamesResultSet, contentOutputTSV, contentTypeTextTSV); - ResponseOps.put(shortNamesResultSet, contentOutputThrift, contentTypeResultsThrift); - } - - interface OutputContent { void output(OutputStream out) throws IOException; } - public static void doResponseResultSet(HttpAction action, Boolean booleanResult) { - doResponseResultSet$(action, null, booleanResult, null, DEF.rsOfferBoolean); + Responses.doResponseResultSet(action, booleanResult); } public static void doResponseResultSet(HttpAction action, ResultSet resultSet, Prologue qPrologue) { - doResponseResultSet$(action, resultSet, null, qPrologue, DEF.rsOfferTable); - } - - // One or the other argument must be null - private static void doResponseResultSet$(HttpAction action, - ResultSet resultSet, Boolean booleanResult, - Prologue qPrologue, AcceptList contentTypeOffer) { - HttpServletRequest request = action.getRequest(); - long id = action.id; - - if ( resultSet == null && booleanResult == null ) { - xlog.warn("doResponseResult: Both result set and boolean result are null"); - throw new FusekiException("Both result set and boolean result are null"); - } - - if ( resultSet != null && booleanResult != null ) { - xlog.warn("doResponseResult: Both result set and boolean result are set"); - throw new FusekiException("Both result set and boolean result are set"); - } - - String mimeType = null; - // -- Conneg - MediaType i = ConNeg.chooseContentType(request, contentTypeOffer, DEF.acceptResultSetXML); - if ( i != null ) - mimeType = i.getContentTypeStr(); - - // -- Override content type from conneg. - // Does &output= override? - // Requested output type by the web form or &output= in the request. - String outputField = ResponseOps.paramOutput(request, shortNamesResultSet); // Expands short names - if ( outputField != null ) - mimeType = outputField; - - String serializationType = mimeType; // Choose the serializer based on this. - String contentType = mimeType; // Set the HTTP response header to this. - - // -- Stylesheet - change to application/xml. - final String stylesheetURL = ResponseOps.paramStylesheet(request); - if ( stylesheetURL != null && Objects.equals(serializationType,contentTypeResultsXML) ) - contentType = contentTypeXML; - - // Force to text/plain? - String forceAccept = ResponseOps.paramForceAccept(request); - if ( forceAccept != null ) - contentType = contentTypeTextPlain; - - // Some kind of general dispatch is neater but there are quite a few special cases. - // text/plain is special because there is no ResultSetWriter for it (yet). - // Text plain is special because of the formatting by prologue. - // text/plain is not a registered result set language. - // - // JSON is special because of ?callback - // - // XML is special because of - // (1) charset is a feature of XML, not the response - // (2) ?stylesheet= - // - // Thrift is special because - // (1) charset is meaningless - // (2) there is no boolean result form. - - if ( Objects.equals(serializationType, contentTypeTextPlain) ) { - textOutput(action, contentType, resultSet, qPrologue, booleanResult); - return; - } - - Lang lang = WebContent.contentTypeToLangResultSet(serializationType); - if (lang == null ) - ServletOps.errorBadRequest("Not recognized for SPARQL results: "+serializationType); - if ( ! ResultSetWriterRegistry.isRegistered(lang) ) - ServletOps.errorBadRequest("No results writer for "+serializationType); - - Context cxt = action.getContext().copy(); - String charset = charsetUTF8; - String jsonCallback = null; - - if ( Objects.equals(serializationType, contentTypeResultsXML) ) { - charset = null; - if ( stylesheetURL != null ) - cxt.set(RowSetWriterXML.xmlStylesheet, stylesheetURL); - } - if ( Objects.equals(serializationType, contentTypeResultsJSON) ) { - jsonCallback = ResponseOps.paramCallback(action.getRequest()); - } - if (Objects.equals(serializationType, WebContent.contentTypeResultsThrift) ) { - if ( booleanResult != null ) - ServletOps.errorBadRequest("Can't write a boolean result in thrift"); - charset = null; - } - if (Objects.equals(serializationType, WebContent.contentTypeResultsProtobuf) ) { - if ( booleanResult != null ) - ServletOps.errorBadRequest("Can't write a boolean result in protobuf"); - charset = null; - } - - - // Finally, the general case - generalOutput(action, lang, contentType, charset, cxt, jsonCallback, resultSet, booleanResult); - } - - private static void textOutput(HttpAction action, String contentType, ResultSet resultSet, Prologue qPrologue, Boolean booleanResult) { - // Text is not streaming. - OutputContent proc = out -> { - if ( resultSet != null ) - ResultSetFormatter.out(out, resultSet, qPrologue); - if ( booleanResult != null ) - ResultSetFormatter.out(out, booleanResult.booleanValue()); - }; - - output(action, contentType, charsetUTF8, proc); - } - - /** Any format */ - private static void generalOutput(HttpAction action, Lang rsLang, - String contentType, String charset, - Context context, String callback, - ResultSet resultSet, Boolean booleanResult) { - ResultsWriter rw = ResultsWriter.create() - .lang(rsLang) - .context(context) - .build(); - OutputContent proc = (out) -> { - if ( callback != null ) { - String callbackFunction = callback; - callbackFunction = callbackFunction.replace("\r", ""); - callbackFunction = callbackFunction.replace("\n", ""); - out.write(StrUtils.asUTF8bytes(callbackFunction)); - out.write('('); out.write('\n'); - } - if ( resultSet != null ) - rw.write(out, resultSet); - if ( booleanResult != null ) - rw.write(out, booleanResult.booleanValue()); - if ( callback != null ) { - out.write(')'); out.write('\n'); - } - }; - output(action, contentType, charset, proc); - } - - // Set HTTP response and execute OutputContent inside try-catch. - private static void output(HttpAction action, String contentType, String charset, OutputContent proc) { - try { - ResponseOps.setHttpResponse(action, contentType, charset); - ServletOps.success(action); - OutputStream out = action.getResponseOutputStream(); - try { - proc.output(out); - out.flush(); - } catch (QueryCancelledException ex) { - // Status code 200 may have already been sent. - // We can try to set the HTTP response code anyway. - // Breaking the results is the best we can do to indicate the timeout. - action.setResponseStatus(HttpSC.BAD_REQUEST_400); - action.log.info(format("[%d] Query Cancelled - results truncated (but 200 may have already been sent)", action.id)); - PrintStream ps = new PrintStream(out); - ps.println(); - ps.println("## Query cancelled due to timeout during execution ##"); - ps.println("## **** Incomplete results **** ##"); - ps.flush(); - out.flush(); - // No point raising an exception - 200 was sent already. - //errorOccurred(ex); - } - // Includes client gone. - } catch (IOException ex) { ServletOps.errorOccurred(ex); } - // Do not call httpResponse.flushBuffer() at this point. JSON callback closing details haven't been added. - // Jetty closes the stream if it is a gzip stream. + RowSet rowSet = RowSet.adapt(resultSet); + Responses.doResponseResultSet(action, rowSet, qPrologue); } } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/Responses.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/Responses.java new file mode 100644 index 0000000000..a0943fdb6f --- /dev/null +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/Responses.java @@ -0,0 +1,526 @@ +/* + * 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.fuseki.servlets; + +import static java.lang.String.format; +import static org.apache.jena.atlas.lib.Lib.lowercase; +import static org.apache.jena.riot.WebContent.*; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.apache.jena.atlas.json.JsonObject; +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.atlas.lib.StrUtils; +import org.apache.jena.atlas.web.AcceptList; +import org.apache.jena.atlas.web.MediaType; +import org.apache.jena.fuseki.DEF; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiException; +import org.apache.jena.fuseki.system.ConNeg; +import org.apache.jena.fuseki.system.FusekiNetLib; +import org.apache.jena.graph.Graph; +import org.apache.jena.query.QueryCancelledException; +import org.apache.jena.query.ResultSetFormatter; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFFormat; +import org.apache.jena.riot.RDFLanguages; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.resultset.ResultSetWriterRegistry; +import org.apache.jena.riot.rowset.rw.RowSetWriterXML; +import org.apache.jena.riot.web.HttpNames; +import org.apache.jena.shared.JenaException; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.core.Prologue; +import org.apache.jena.sparql.exec.RowSet; +import org.apache.jena.sparql.exec.RowSetOps; +import org.apache.jena.sparql.resultset.ResultsWriter; +import org.apache.jena.sparql.util.Context; +import org.apache.jena.web.HttpSC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This is the content negotiation for each kind of SPARQL query result */ +public class Responses +{ + private static Logger xlog = LoggerFactory.getLogger(Responses.class); + + // Short names for result sets "output=" + private static final String contentOutputJSON = "json"; + private static final String contentOutputXML = "xml"; + private static final String contentOutputSPARQL = "sparql"; + private static final String contentOutputText = "text"; + private static final String contentOutputCSV = "csv"; + private static final String contentOutputTSV = "tsv"; + private static final String contentOutputThrift = "thrift"; + + // Short names for graph and datasets "output=" + private static final String contentOutputJSONLD = "json-ld"; + private static final String contentOutputJSONRDF = "json-rdf"; + private static final String contentOutputTTL = "ttl"; + private static final String contentOutputTurtle = "turtle"; + private static final String contentOutputNT = "nt"; + private static final String contentOutputTriG = "trig"; + private static final String contentOutputNQuads = "n-quads"; + + + // Short names - result sets + public static final Map<String,String> shortNamesResultSet = Map.ofEntries + (entryLC(contentOutputJSON, contentTypeResultsJSON), + entryLC(contentOutputSPARQL, contentTypeResultsXML), + entryLC(contentOutputXML, contentTypeResultsXML), + entryLC(contentOutputText, contentTypeTextPlain), + entryLC(contentOutputCSV, contentTypeTextCSV), + entryLC(contentOutputTSV, contentTypeTextTSV), + entryLC(contentOutputThrift, contentTypeResultsThrift) + ); + + // Short names - graphs etc + public static final Map<String,String> shortNamesGraph = Map.ofEntries + (entryLC(contentOutputJSONLD, contentTypeJSONLD), + entryLC(contentOutputJSONRDF, contentTypeRDFJSON), + entryLC(contentOutputJSON, contentTypeJSONLD), + entryLC(contentOutputXML, contentTypeRDFXML), + entryLC(contentOutputText, contentTypeTurtle), + entryLC(contentOutputTTL, contentTypeTurtle), + entryLC(contentOutputTurtle, contentTypeTurtle), + entryLC(contentOutputNT, contentTypeNTriples), + entryLC(contentOutputNQuads, contentTypeNQuads), + entryLC(contentOutputTriG, contentTypeTriG) + ); + + private static Map.Entry<String, String> entryLC(String key, String value) { + return Map.entry(Lib.lowercase(key), value); + } + + interface OutputContent { void output(OutputStream out) throws IOException; } + + // Set HTTP response and execute OutputContent inside try-catch. + private static void output(HttpAction action, String contentType, String charset, OutputContent proc) { + try { + setHttpResponse(action, contentType, charset); + ServletOps.success(action); + OutputStream out = action.getResponseOutputStream(); + try { + proc.output(out); + out.flush(); + } catch (QueryCancelledException ex) { + // Status code 200 may have already been sent. + // We can try to set the HTTP response code anyway. + // Breaking the results is the best we can do to indicate the timeout. + action.setResponseStatus(HttpSC.BAD_REQUEST_400); + action.log.info(format("[%d] Query Cancelled - results truncated (but 200 may have already been sent)", action.id)); + PrintStream ps = new PrintStream(out); + ps.println(); + ps.println("## Query cancelled due to timeout during execution ##"); + ps.println("## **** Incomplete results **** ##"); + ps.flush(); + out.flush(); + // No point raising an exception - 200 was sent already. + //errorOccurred(ex); + } + // Includes client gone. + } catch (IOException ex) { ServletOps.errorOccurred(ex); } + // Do not call httpResponse.flushBuffer() at this point. JSON callback closing details haven't been added. + // Jetty closes the stream if it is a gzip stream. + } + + public static void doResponseResultSet(HttpAction action, Boolean booleanResult) { + ResponseResults.doResponseResultSet$(action, null, booleanResult, null, DEF.rsOfferBoolean); + } + + public static void doResponseResultSet(HttpAction action, RowSet rowSet, Prologue qPrologue) { + ResponseResults.doResponseResultSet$(action, rowSet, null, qPrologue, DEF.rsOfferTable); + } + + static class ResponseResults { + // One or the other argument must be null + private static void doResponseResultSet$(HttpAction action, + RowSet rowSet, Boolean booleanResult, + Prologue qPrologue, AcceptList contentTypeOffer) { + HttpServletRequest request = action.getRequest(); + long id = action.id; + + if ( rowSet == null && booleanResult == null ) { + xlog.warn("doResponseResult: Both result set and boolean result are null"); + throw new FusekiException("Both result set and boolean result are null"); + } + + if ( rowSet != null && booleanResult != null ) { + xlog.warn("doResponseResult: Both result set and boolean result are set"); + throw new FusekiException("Both result set and boolean result are set"); + } + + String mimeType = null; + // -- Conneg + MediaType i = ConNeg.chooseContentType(request, contentTypeOffer, DEF.acceptResultSetXML); + if ( i != null ) + mimeType = i.getContentTypeStr(); + + // -- Override content type from conneg. + // Does &output= override? + // Requested output type by the web form or &output= in the request. + String outputField = paramOutput(request, shortNamesResultSet); // Expands short names + if ( outputField != null ) + mimeType = outputField; + + String serializationType = mimeType; // Choose the serializer based on this. + String contentType = mimeType; // Set the HTTP response header to this. + + // -- Stylesheet - change to application/xml. + final String stylesheetURL = paramStylesheet(request); + if ( stylesheetURL != null && Objects.equals(serializationType,contentTypeResultsXML) ) + contentType = contentTypeXML; + + // Force to text/plain? + String forceAccept = paramForceAccept(request); + if ( forceAccept != null ) + contentType = contentTypeTextPlain; + + // Some kind of general dispatch is neater but there are quite a few special cases. + // text/plain is special because there is no ResultSetWriter for it (yet). + // Text plain is special because of the formatting by prologue. + // text/plain is not a registered result set language. + // + // JSON is special because of ?callback + // + // XML is special because of + // (1) charset is a feature of XML, not the response + // (2) ?stylesheet= + // + // Thrift is special because + // (1) charset is meaningless + // (2) there is no boolean result form. + + if ( Objects.equals(serializationType, contentTypeTextPlain) ) { + textOutput(action, contentType, rowSet, qPrologue, booleanResult); + return; + } + + Lang lang = WebContent.contentTypeToLangResultSet(serializationType); + if (lang == null ) + ServletOps.errorBadRequest("Not recognized for SPARQL results: "+serializationType); + if ( ! ResultSetWriterRegistry.isRegistered(lang) ) + ServletOps.errorBadRequest("No results writer for "+serializationType); + + Context cxt = action.getContext().copy(); + String charset = charsetUTF8; + String jsonCallback = null; + + if ( Objects.equals(serializationType, contentTypeResultsXML) ) { + charset = null; + if ( stylesheetURL != null ) + cxt.set(RowSetWriterXML.xmlStylesheet, stylesheetURL); + } + if ( Objects.equals(serializationType, contentTypeResultsJSON) ) { + jsonCallback = paramCallback(action.getRequest()); + } + if (Objects.equals(serializationType, WebContent.contentTypeResultsThrift) ) { + if ( booleanResult != null ) + ServletOps.errorBadRequest("Can't write a boolean result in thrift"); + charset = null; + } + if (Objects.equals(serializationType, WebContent.contentTypeResultsProtobuf) ) { + if ( booleanResult != null ) + ServletOps.errorBadRequest("Can't write a boolean result in protobuf"); + charset = null; + } + + // Finally, the general case + generalOutput(action, lang, contentType, charset, cxt, jsonCallback, rowSet, booleanResult); + } + + private static void textOutput(HttpAction action, String contentType, RowSet resultSet, Prologue qPrologue, Boolean booleanResult) { + // Text is not streaming. + OutputContent proc = out -> { + if ( resultSet != null ) + RowSetOps.out(out, resultSet, qPrologue); + if ( booleanResult != null ) + ResultSetFormatter.out(out, booleanResult.booleanValue()); + }; + + output(action, contentType, charsetUTF8, proc); + } + + /** Any format */ + private static void generalOutput(HttpAction action, Lang rsLang, + String contentType, String charset, + Context context, String callback, + RowSet resultSet, Boolean booleanResult) { + ResultsWriter rw = ResultsWriter.create() + .lang(rsLang) + .context(context) + .build(); + OutputContent proc = (out) -> { + if ( callback != null ) { + String callbackFunction = callback; + callbackFunction = callbackFunction.replace("\r", ""); + callbackFunction = callbackFunction.replace("\n", ""); + out.write(StrUtils.asUTF8bytes(callbackFunction)); + out.write('('); out.write('\n'); + } + if ( resultSet != null ) + rw.write(out, resultSet); + if ( booleanResult != null ) + rw.write(out, booleanResult.booleanValue()); + if ( callback != null ) { + out.write(')'); out.write('\n'); + } + }; + output(action, contentType, charset, proc); + } + } + + public static void doResponseGraph(HttpAction action, Graph graph) { + DatasetGraph ds = DatasetGraphFactory.wrap(graph); + doResponseDataset(action, ds); + } + + public static void doResponseDataset(HttpAction action, DatasetGraph dataset) { + ResponseGraph.doResponseDataset$(action, dataset); + } + + + static class ResponseGraph { + static void doResponseDataset$(HttpAction action, DatasetGraph dataset) { + HttpServletRequest request = action.getRequest(); + + String mimeType = null; // Header request type + + MediaType i = ConNeg.chooseContentType(request, DEF.constructOffer, DEF.acceptTurtle); + if ( i != null ) + mimeType = i.getContentTypeStr(); + + String outputField = paramOutput(request, shortNamesGraph); + if ( outputField != null ) + mimeType = outputField; + + String writerMimeType = mimeType; + + if ( mimeType == null ) { + Fuseki.actionLog.warn("Can't find MIME type for response"); + String x = FusekiNetLib.getAccept(request); + String msg; + if ( x == null ) + msg = "No Accept: header"; + else + msg = "Accept: " + x + " : Not understood"; + ServletOps.error(HttpSC.NOT_ACCEPTABLE_406, msg); + } + + String contentType = mimeType; + String charset = charsetUTF8; + + String forceAccept = paramForceAccept(request); + if ( forceAccept != null ) { + contentType = forceAccept; + charset = charsetUTF8; + } + + Lang lang = RDFLanguages.contentTypeToLang(contentType); + if ( lang == null ) + ServletOps.errorBadRequest("Can't determine output content type: "+contentType); + RDFFormat format = ActionLib.getNetworkFormatForLang(lang); + + try { + ServletOps.success(action); + ServletOutputStream out = action.getResponseOutputStream(); + try { + // Use the Content-Type from the content negotiation. + if ( RDFLanguages.isQuads(lang) ) + ActionLib.datasetResponse(action, dataset, format, contentType); + else + ActionLib.graphResponse(action, dataset.getDefaultGraph(), format, contentType); + out.flush(); + } catch (JenaException ex) { + ServletOps.errorOccurred("Failed to write output: "+ex.getMessage(), ex); + } + } + catch (ActionErrorException ex) { throw ex; } + catch (Exception ex) { + action.log.info("Exception while writing the response model: "+ex.getMessage(), ex); + ServletOps.errorOccurred("Exception while writing the response model: "+ex.getMessage(), ex); + } + } + } + + + public static void doResponseJson(HttpAction action, Iterator<JsonObject> jsonItem) { + ResponseJson.doResponseJson$(action, jsonItem); + } + + static class ResponseJson { + /** + * Outputs a JSON query result + * + * @param action HTTP action + * @param jsonItem a ResultSetJsonStream instance + */ + static void doResponseJson$(HttpAction action, Iterator<JsonObject> jsonItem) { + if ( jsonItem == null ) { + xlog.warn("doResponseJson: Result set is null"); + throw new FusekiException("Result set is null"); + } + + jsonOutput(action, jsonItem); + } + + private static void jsonOutput(HttpAction action, final Iterator<JsonObject> jsonItems) { + OutputContent proc = out-> { + if ( jsonItems != null ) + ResultSetFormatter.output(out, jsonItems); + }; + + try { + String callback = paramCallback(action.getRequest()); + ServletOutputStream out = action.getResponseOutputStream(); + + if ( callback != null ) { + callback = StringUtils.replaceChars(callback, "\r", ""); + callback = StringUtils.replaceChars(callback, "\n", ""); + out.write(StrUtils.asUTF8bytes(callback)); + out.write('('); out.write('\n'); + } + + output(action, "application/json", WebContent.charsetUTF8, proc); + + if ( callback != null ) { + out.write(')'); out.write('\n'); + } + } catch (IOException ex) { + ServletOps.errorOccurred(ex); + } + } + + private static void output(HttpAction action, String contentType, String charset, OutputContent proc) { + try { + setHttpResponse(action, contentType, charset); + action.setResponseStatus(HttpSC.OK_200); + ServletOutputStream out = action.getResponseOutputStream(); + try { + proc.output(out); + out.flush(); + } catch (QueryCancelledException ex) { + // Bother. Status code 200 already sent. + xlog.info(format("[%d] Query Cancelled - results truncated (but 200 already sent)", action.id)); + PrintStream ps = new PrintStream(out); + ps.println(); + ps.println("## Query cancelled due to timeout during execution ##"); + ps.println("## **** Incomplete results **** ##"); + ps.flush(); + out.flush(); + // No point raising an exception - 200 was sent already. + // errorOccurred(ex); + } + // Includes client gone. + } catch (IOException ex) { + ServletOps.errorOccurred(ex); + } + // Do not call httpResponse.flushBuffer(); here - Jetty closes the stream if + // it is a gzip stream + // then the JSON callback closing details can't be added. + } + + public static void setHttpResponse(HttpAction action, String contentType, String charset) { + // ---- Set up HTTP Response + // Stop caching (not that ?queryString URLs are cached anyway) + ServletOps.setNoCache(action); + // See: http://www.w3.org/International/O-HTTP-charset.html + if ( contentType != null ) { + if ( charset != null ) + contentType = contentType + "; charset=" + charset; + xlog.trace("Content-Type for response: " + contentType); + action.setResponseContentType(contentType); + } + } + } + + static String paramForceAccept(HttpServletRequest request) { + String x = fetchParam(request, HttpNames.paramForceAccept); + return x; + } + + static String paramStylesheet(HttpServletRequest request) { + return fetchParam(request, HttpNames.paramStyleSheet); + } + + static String paramOutput(HttpServletRequest request, Map<String, String> map) { + // Two names. + String x = fetchParam(request, HttpNames.paramOutput1); + if ( x == null ) + x = fetchParam(request, HttpNames.paramOutput2); + if ( x == null ) + x = fetchParam(request, HttpNames.paramOutput3); + return expandShortName(x, map); + } + + private static String expandShortName(String str, Map<String, String> map) { + if ( str == null ) + return null; + // Force keys to lower case. See put() above. + String key = lowercase(str); + String str2 = map.get(key); + if ( str2 == null ) + return str; + return str2; + } + + static String paramCallback(HttpServletRequest request) { + return fetchParam(request, HttpNames.paramCallback); + } + + private static String fetchParam(HttpServletRequest request, String parameterName) { + String value = request.getParameter(parameterName); + if ( value != null ) { + value = value.trim(); + if ( value.length() == 0 ) + value = null; + } + return value; + } + + /** Basic settings, including Content-Type, for a response. */ + static void setHttpResponse(HttpAction action, String contentType, String charset) { + // ---- Set up HTTP Response + // Stop caching (not that ?queryString URLs are cached anyway) + if ( true ) + ServletOps.setNoCache(action); + // See: http://www.w3.org/International/O-HTTP-charset.html + if ( contentType != null ) { + if ( charset != null && !isXML(contentType) ) + contentType = contentType + "; charset=" + charset; + action.log.trace("Content-Type for response: " + contentType); + action.setResponseContentType(contentType); + } + } + + private static boolean isXML(String contentType) { + return contentType.equals(WebContent.contentTypeRDFXML) || contentType.equals(WebContent.contentTypeResultsXML) + || contentType.equals(WebContent.contentTypeXML); + } + +} diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java index f0b4199ce9..385594535e 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQLQueryProcessor.java @@ -33,7 +33,6 @@ import java.util.*; import java.util.concurrent.TimeUnit; import jakarta.servlet.http.HttpServletRequest; - import org.apache.jena.atlas.io.IO; import org.apache.jena.atlas.io.IndentedLineBuffer; import org.apache.jena.atlas.json.JsonObject; @@ -41,16 +40,16 @@ import org.apache.jena.atlas.lib.Pair; import org.apache.jena.atlas.web.ContentType; import org.apache.jena.fuseki.Fuseki; import org.apache.jena.fuseki.system.FusekiNetLib; +import org.apache.jena.graph.Graph; import org.apache.jena.query.*; -import org.apache.jena.rdf.model.Model; import org.apache.jena.riot.web.HttpNames; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.Prologue; import org.apache.jena.sparql.engine.Timeouts; import org.apache.jena.sparql.exec.QueryExec; import org.apache.jena.sparql.exec.QueryExecDatasetBuilder; -import org.apache.jena.sparql.exec.QueryExecutionAdapter; -import org.apache.jena.sparql.resultset.SPARQLResult; +import org.apache.jena.sparql.exec.QueryExecResult; +import org.apache.jena.sparql.exec.RowSet; import org.apache.jena.web.HttpSC; /** @@ -276,8 +275,8 @@ public abstract class SPARQLQueryProcessor extends ActionService if ( q == null ) q = query; - try ( QueryExecution qExec = createQueryExecution(action, q, dataset); ) { - SPARQLResult result = executeQuery(action, qExec, query, queryStringLog); + try ( QueryExec qExec = createQueryExec(action, q, dataset); ) { + QueryExecResult result = executeQuery(action, qExec, query, queryStringLog); // Deals with exceptions itself. sendResults(action, result, query.getPrologue()); } @@ -313,7 +312,7 @@ public abstract class SPARQLQueryProcessor extends ActionService * @param dataset * @return QueryExecution */ - protected QueryExecution createQueryExecution(HttpAction action, Query query, DatasetGraph dataset) { + protected QueryExec createQueryExec(HttpAction action, Query query, DatasetGraph dataset) { QueryExecDatasetBuilder builder = QueryExec.newBuilder() .dataset(dataset) .query(query) @@ -321,7 +320,7 @@ public abstract class SPARQLQueryProcessor extends ActionService ; setTimeouts(builder, action); QueryExec qExec = builder.build(); - return QueryExecutionAdapter.adapt(qExec); + return qExec; } /** @@ -376,9 +375,9 @@ public abstract class SPARQLQueryProcessor extends ActionService * @param queryStringLog Informational string created from the initial query. * @return */ - protected SPARQLResult executeQuery(HttpAction action, QueryExecution queryExecution, Query requestQuery, String queryStringLog) { + protected QueryExecResult executeQuery(HttpAction action, QueryExec queryExecution, Query requestQuery, String queryStringLog) { if ( requestQuery.isSelectType() ) { - ResultSet rs = queryExecution.execSelect(); + RowSet rs = queryExecution.select(); // Force some query execution now. // If the timeout-first-row goes off, the output stream has not @@ -390,33 +389,28 @@ public abstract class SPARQLQueryProcessor extends ActionService // the result now to see if the timeout-end-of-query goes off. // rs = ResultSetFactory.copyResults(rs); - //action.log.info(format("[%d] exec/select", action.id)); - return new SPARQLResult(rs); + return new QueryExecResult(rs); } if ( requestQuery.isConstructType() ) { - Dataset dataset = queryExecution.execConstructDataset(); - //action.log.info(format("[%d] exec/construct", action.id)); - return new SPARQLResult(dataset); + DatasetGraph dataset = queryExecution.constructDataset(); + return new QueryExecResult(dataset); } if ( requestQuery.isDescribeType() ) { - Model model = queryExecution.execDescribe(); - //action.log.info(format("[%d] exec/describe", action.id)); - return new SPARQLResult(model); + Graph graph = queryExecution.describe(); + return new QueryExecResult(graph); } if ( requestQuery.isAskType() ) { - boolean b = queryExecution.execAsk(); - //action.log.info(format("[%d] exec/ask", action.id)); - return new SPARQLResult(b); + boolean b = queryExecution.ask(); + return new QueryExecResult(b); } if ( requestQuery.isJsonType() ) { Iterator<JsonObject> jsonIterator = queryExecution.execJsonItems(); //JsonArray jsonArray = queryExecution.execJson(); - action.log.info(format("[%d] exec/json", action.id)); - return new SPARQLResult(jsonIterator); + return new QueryExecResult(jsonIterator); } ServletOps.errorBadRequest("Unknown query type - " + queryStringLog); @@ -436,19 +430,19 @@ public abstract class SPARQLQueryProcessor extends ActionService * @param result * @param qPrologue */ - protected void sendResults(HttpAction action, SPARQLResult result, Prologue qPrologue) { - if ( result.isResultSet() ) - ResponseResultSet.doResponseResultSet(action, result.getResultSet(), qPrologue); + protected void sendResults(HttpAction action, QueryExecResult result, Prologue qPrologue) { + if ( result.isRowSet() ) + Responses.doResponseResultSet(action, result.rowSet(), qPrologue); else if ( result.isDataset() ) // CONSTRUCT is processed as a extended CONSTRUCT - result is a dataset. - ResponseDataset.doResponseDataset(action, result.getDataset()); - else if ( result.isModel() ) - // DESCRIBE results are models - ResponseDataset.doResponseModel(action, result.getModel()); + Responses.doResponseDataset(action, result.dataset()); + else if ( result.isGraph() ) + // DESCRIBE results are graphs + Responses.doResponseGraph(action, result.graph()); else if ( result.isBoolean() ) - ResponseResultSet.doResponseResultSet(action, result.getBooleanResult()); + Responses.doResponseResultSet(action, result.booleanResult()); else if ( result.isJson() ) - ResponseJson.doResponseJson(action, result.getJsonItems()); + Responses.doResponseJson(action, result.jsonItems()); else ServletOps.errorOccurred("Unknown or invalid result type"); } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/GraphLoadUtils.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/GraphLoadUtils.java index a4a5c95013..495cb6e88e 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/GraphLoadUtils.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/GraphLoadUtils.java @@ -20,18 +20,18 @@ package org.apache.jena.fuseki.system; import org.apache.jena.fuseki.Fuseki; -import org.apache.jena.graph.GraphMemFactory; import org.apache.jena.graph.Graph; +import org.apache.jena.graph.GraphMemFactory; import org.apache.jena.riot.RDFParser; import org.apache.jena.riot.system.StreamRDF; import org.apache.jena.riot.system.StreamRDFLib; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; -/** A packaging of code to do a controlled read of a graph or model */ +/** A packaging of code to do a controlled read of a graph or */ public class GraphLoadUtils { - // ---- Graph level - public static Graph readGraph(String uri, int limit) { Graph g = GraphMemFactory.createDefaultGraphSameTerm(); readUtil(g, uri, limit); @@ -42,10 +42,27 @@ public class GraphLoadUtils readUtil(g, uri, limit); } - // ** Worker. + public static DatasetGraph readDataset(String uri, int limit) { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + readUtil(dsg, uri, limit); + return dsg; + } + + public static void loadDataset(DatasetGraph dsg, String uri, int limit) { + readUtil(dsg, uri, limit); + } + + // Worker/Graph private static void readUtil(Graph graph, String uri, int limit) { StreamRDF sink = StreamRDFLib.graph(graph); sink = new StreamRDFLimited(sink, limit); RDFParser.source(uri).streamManager(Fuseki.webStreamManager).parse(sink); } + + // Worker/DatasetGraph + private static void readUtil(DatasetGraph dsg, String uri, int limit) { + StreamRDF sink = StreamRDFLib.dataset(dsg); + sink = new StreamRDFLimited(sink, limit); + RDFParser.source(uri).streamManager(Fuseki.webStreamManager).parse(sink); + } }
