This is an automated email from the ASF dual-hosted git repository.
afs pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/main by this push:
new 81b308a436 GH-4055: Pass context into update operations
81b308a436 is described below
commit 81b308a4367cc2bc62f781609de1759cb9168cc2
Author: Andy Seaborne <[email protected]>
AuthorDate: Tue Jun 16 16:44:09 2026 +0100
GH-4055: Pass context into update operations
---
.../org/apache/jena/riot/RDFParserBuilder.java | 8 +--
.../jena/riot/system/streammgr/StreamManager.java | 7 ++
.../apache/jena/sparql/engine/QueryEngineBase.java | 5 ++
.../org/apache/jena/sparql/lang/UpdateParser.java | 13 ++--
.../jena/sparql/modify/UpdateEngineWorker.java | 72 ++++++++++++++------
.../jena/sparql/modify/UpdateRequestSink.java | 10 +--
.../jena/sparql/modify/UpdateVisitorSink.java | 8 +--
.../org/apache/jena/sparql/modify/UsingList.java | 49 ++++++++++++--
.../apache/jena/sparql/modify/UsingUpdateSink.java | 16 +----
.../jena/sparql/modify/request/UpdateLoad.java | 1 -
.../sparql/modify/request/UpdateWithUsing.java | 9 +--
.../java/org/apache/jena/sparql/util/Context.java | 2 +
.../java/org/apache/jena/update/UpdateAction.java | 78 +++++++++++++++++++---
.../apache/jena/sparql/modify/TestUpdateBuild.java | 19 ++++++
.../main/java/org/apache/jena/fuseki/Fuseki.java | 2 +-
.../org/apache/jena/fuseki/servlets/ActionLib.java | 7 +-
.../apache/jena/fuseki/servlets/HttpAction.java | 14 +++-
.../org/apache/jena/fuseki/servlets/Responses.java | 3 +-
.../jena/fuseki/servlets/SPARQLQueryProcessor.java | 2 +-
.../apache/jena/fuseki/servlets/SPARQL_Update.java | 31 ++++++---
.../org/apache/jena/fuseki/main/TS_FusekiMain.java | 2 +-
.../{TestUpdate.java => TestSPARQLUpdate.java} | 49 +++++++++++++-
.../jena-fuseki-main/testing/Files/data.ttl | 3 +
23 files changed, 317 insertions(+), 93 deletions(-)
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/RDFParserBuilder.java
b/jena-arq/src/main/java/org/apache/jena/riot/RDFParserBuilder.java
index 3e6174a71d..cea658f51e 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/RDFParserBuilder.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/RDFParserBuilder.java
@@ -610,12 +610,12 @@ public class RDFParserBuilder {
else
parserBaseURI = null;
- StreamManager sMgr = streamManager;
- if ( sMgr == null )
- sMgr = StreamManager.get(context);
+ StreamManager streamMgr = streamManager;
+ if ( streamMgr == null )
+ streamMgr = StreamManager.get(context);
// Can't build the profile here as it is Lang/conneg dependent.
- return new RDFParser(uri, path, stringToParse, inputStream,
javaReader, sMgr,
+ return new RDFParser(uri, path, stringToParse, inputStream,
javaReader, streamMgr,
appAcceptHeader, httpHeaders,
httpClient,
hintLang, forceLang,
diff --git
a/jena-arq/src/main/java/org/apache/jena/riot/system/streammgr/StreamManager.java
b/jena-arq/src/main/java/org/apache/jena/riot/system/streammgr/StreamManager.java
index 423fb346ca..2096ccc42a 100644
---
a/jena-arq/src/main/java/org/apache/jena/riot/system/streammgr/StreamManager.java
+++
b/jena-arq/src/main/java/org/apache/jena/riot/system/streammgr/StreamManager.java
@@ -90,6 +90,13 @@ public class StreamManager {
return get();
}
+ /**
+ * Set the {@code StreamManager} in the context.
+ */
+ public static void set(Context context, StreamManager streamManager) {
+ context.set(SysRIOT.sysStreamManager, streamManager);
+ }
+
/**
* Set the global {@code StreamManager}.
*/
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/engine/QueryEngineBase.java
b/jena-arq/src/main/java/org/apache/jena/sparql/engine/QueryEngineBase.java
index f9daf0f172..f9427eea7d 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/QueryEngineBase.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/QueryEngineBase.java
@@ -77,6 +77,11 @@ public abstract class QueryEngineBase implements OpEval,
Closeable
if ( ! query.hasDatasetDescription() )
throw new QueryExecException("No dataset and no dataset
description for query");
+ // dsg == null.
+ // This is the only case where the code will process FROM/FROM NAMED by
+ // reading resources. When dgs != null, FROM/FROM NAMED pick graphs
from
+ // the dataset and form a dynamic dataset.
+
// DatasetDescription : Build it.
String baseURI = query.getBaseURI();
if ( baseURI == null )
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/lang/UpdateParser.java
b/jena-arq/src/main/java/org/apache/jena/sparql/lang/UpdateParser.java
index 2832db29f4..3988e0b40b 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/lang/UpdateParser.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/lang/UpdateParser.java
@@ -33,25 +33,24 @@ import org.apache.jena.sparql.core.Prologue;
import org.apache.jena.sparql.modify.UpdateSink;
import org.apache.jena.util.FileUtils;
-/**
+/**
* This class provides the root of lower level access to all the update
parsers.
* Each subclass hides the details of the per-language exception handlers and
other
- * javacc details.
+ * javacc details.
*/
public abstract class UpdateParser
{
protected UpdateParser() {}
-
- /** Parse a string */
+
+ /** Parse a string */
public final void parse(UpdateSink sink, Prologue prologue, String
updateString) throws QueryParseException {
Reader r = new StringReader(updateString);
executeParse(sink, prologue, r);
}
- /** Parse an input stream */
+ /** Parse an input stream */
public final void parse(UpdateSink sink, Prologue prologue, InputStream
input) throws QueryParseException {
- // BOM processing moved to the grammar.
Reader r = FileUtils.asBufferedUTF8(input);
executeParse(sink, prologue, r);
}
@@ -65,7 +64,7 @@ public abstract class UpdateParser
// Subclass action.
protected abstract void executeParse(UpdateSink sink, Prologue prologue,
Reader r);
-
+
public static boolean canParse(Syntax syntaxURI) {
return UpdateParserRegistry.get().containsFactory(syntaxURI);
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateEngineWorker.java
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateEngineWorker.java
index b54aba39d6..cfa6099ca7 100644
---
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateEngineWorker.java
+++
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateEngineWorker.java
@@ -24,6 +24,7 @@ package org.apache.jena.sparql.modify;
import static org.apache.jena.sparql.modify.TemplateLib.remapDefaultGraph;
import static org.apache.jena.sparql.modify.TemplateLib.template;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -44,6 +45,8 @@ import org.apache.jena.graph.Node;
import org.apache.jena.query.Query;
import org.apache.jena.riot.*;
import org.apache.jena.riot.system.PrefixMap;
+import org.apache.jena.riot.system.StreamRDF;
+import org.apache.jena.riot.system.StreamRDFLib;
import org.apache.jena.sparql.ARQInternalErrorException;
import org.apache.jena.sparql.core.*;
import org.apache.jena.sparql.engine.Timeouts;
@@ -142,7 +145,7 @@ public class UpdateEngineWorker implements UpdateVisitor
boolean auto = autoSilent && !isClear;
executeOperation( auto || update.isSilent(), () -> {
if ( g != null && !datasetGraph.containsGraph(g) )
- throw errorEx("No such graph: " + g);
+ throw errorEx("No such graph: " + g);
if ( isClear ) {
if ( g == null || datasetGraph.containsGraph(g) )
graphOrThrow(datasetGraph, g).clear();
@@ -190,6 +193,7 @@ public class UpdateEngineWorker implements UpdateVisitor
// LOAD SILENT? iri ( INTO GraphRef )?
String source = update.getSource();
Node dest = update.getDest();
+
executeOperation(update.isSilent(), ()->{
Graph graph = graphOrThrow(datasetGraph, dest);
// We must load buffered if silent so that the dataset graph sees
@@ -198,39 +202,30 @@ public class UpdateEngineWorker implements UpdateVisitor
try {
boolean loadBuffered = update.isSilent() || !
datasetGraph.supportsTransactionAbort();
if ( dest == null ) {
- // LOAD SILENT? iri
+ // LOAD SILENT? iri -- no INTO
// Quads accepted (extension).
if ( loadBuffered ) {
DatasetGraph dsg2 = DatasetGraphFactory.create();
- RDFDataMgr.read(dsg2, source);
+ loadReadQuads(source, dsg2, context);
+ // Parsing seceded.
dsg2.find().forEachRemaining(datasetGraph::add);
} else {
- RDFDataMgr.read(datasetGraph, source);
+ // Transactional and not SILENT.
+ loadReadQuads(source, datasetGraph, context);
}
return;
}
// LOAD SILENT? iri INTO GraphRef
- // Load triples. To give a decent error message and also not
have the usual
- // parser behaviour of just selecting default graph triples
when the
- // destination is a graph, we need to do the same steps as
RDFParser.parseURI,
- // with different checking.
+ // Load triples.
TypedInputStream input = RDFDataMgr.open(source);
- String contentType = input.getContentType();
- Lang lang = RDFDataMgr.determineLang(source, contentType,
Lang.TTL);
- if ( lang == null )
- throw new UpdateException("Failed to determine the syntax
for '"+source+"'");
- if ( ! RDFLanguages.isTriples(lang) )
- throw new UpdateException("Attempt to load quads into a
graph");
- RDFParser parser = RDFParser
- .source(input.getInputStream())
- .forceLang(lang)
- .build();
+ Lang lang = determineLang(source, input);
+
if ( loadBuffered ) {
Graph g = GraphFactory.createGraphMem();
- parser.parse(g);
+ loadReadTriples(input, lang, g, context);
GraphUtil.addInto(graph, g);
} else {
- parser.parse(graph);
+ loadReadTriples(input, lang, graph, context);
}
} catch (RiotException ex) {
if ( !update.isSilent() ) {
@@ -240,6 +235,43 @@ public class UpdateEngineWorker implements UpdateVisitor
});
}
+ // To give a decent error message, and also not have the usual
+ // parser behaviour of just selecting default graph triples when
+ // the destination is a graph, we need to do the same steps as
+ // RDFParser.parseURI with different checking.
+ private static Lang determineLang(String source, TypedInputStream input) {
+ String contentType = input.getContentType();
+ Lang lang = RDFDataMgr.determineLang(source, contentType, Lang.TTL);
+ if ( lang == null )
+ throw new UpdateException("Failed to determine the syntax for
'"+source+"'");
+ if ( ! RDFLanguages.isTriples(lang) )
+ throw new UpdateException("Attempt to load quads into a graph");
+ return lang;
+ }
+
+ /** Load data into a dataset graph. The context has the stream manager. */
+ private static void loadReadQuads(String source, DatasetGraph destination,
Context context) {
+ StreamRDF parserDest = StreamRDFLib.dataset(destination);
+ // RDFParser picks up the stream manager from the context if not set.
+ //StreamManager streamMgr = StreamManager.get(context);
+ RDFParser.source(source)
+ //.streamManager(streamMgr)
+ .context(context)
+ .parse(parserDest);
+ }
+
+ /** Load data into a graph. The context has the stream manager. */
+ private static void loadReadTriples(InputStream input, Lang lang, Graph
destination, Context context) {
+ StreamRDF parserDest = StreamRDFLib.graph(destination);
+ // RDFParser picks up the stream manager from the context if not set.
+ //StreamManager streamMgr = StreamManager.get(context);
+ RDFParser.source(input)
+ .forceLang(lang)
+ //.streamManager(streamMgr)
+ .context(context)
+ .parse(parserDest);
+ }
+
@Override
public void visit(UpdateAdd update) {
executeOperation(update.isSilent(), ()->{
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateRequestSink.java
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateRequestSink.java
index 9c862be15c..9a989279be 100644
---
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateRequestSink.java
+++
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateRequestSink.java
@@ -28,10 +28,14 @@ import
org.apache.jena.sparql.modify.request.UpdateDataInsert ;
import org.apache.jena.update.Update ;
import org.apache.jena.update.UpdateRequest ;
+/**
+ * Accumulate {@link Update Updates} (the individual update operations) in an
+ * {@link UpdateRequest}.
+ */
public class UpdateRequestSink implements UpdateSink
{
private final UpdateRequest updateRequest;
-
+
public UpdateRequestSink(UpdateRequest updateRequest) {
this.updateRequest = updateRequest;
}
@@ -44,7 +48,7 @@ public class UpdateRequestSink implements UpdateSink
@Override
public void flush()
{ }
-
+
@Override
public void close()
{ }
@@ -53,7 +57,6 @@ public class UpdateRequestSink implements UpdateSink
public QuadDataAccSink createInsertDataSink() {
QuadDataAcc quads = new QuadDataAcc();
send(new UpdateDataInsert(quads));
-
return quads;
}
@@ -61,7 +64,6 @@ public class UpdateRequestSink implements UpdateSink
public QuadDataAccSink createDeleteDataSink() {
QuadDataAcc quads = new QuadDataAcc();
send(new UpdateDataDelete(quads));
-
return quads;
}
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateVisitorSink.java
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateVisitorSink.java
index 0ef314fb5b..1295ad8107 100644
---
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateVisitorSink.java
+++
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UpdateVisitorSink.java
@@ -28,15 +28,15 @@ import org.apache.jena.sparql.modify.request.UpdateVisitor ;
import org.apache.jena.update.Update ;
/**
- * UpdateSink that sends every Update to a worker except for the quads
- * of INSERT DATA, DELETE DATA which do to special sinks.
+ * UpdateSink that sends every Update to a worker except for the quads
+ * of INSERT DATA, DELETE DATA which go to special sinks.
*/
public class UpdateVisitorSink implements UpdateSink
{
private final UpdateVisitor worker;
private final Sink<Quad> addSink;
private final Sink<Quad> delSink;
-
+
public UpdateVisitorSink(UpdateVisitor worker, Sink<Quad> addSink,
Sink<Quad> delSink) {
this.worker = worker;
this.addSink = addSink;
@@ -47,7 +47,7 @@ public class UpdateVisitorSink implements UpdateSink
public void send(Update update) {
update.visit(worker);
}
-
+
// The sink for INSERT DATA, DELETE DATA to go straight to sink handlers.
@Override
public QuadDataAccSink createInsertDataSink() {
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingList.java
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingList.java
index 272cf7c49a..9a9e71e014 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingList.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingList.java
@@ -27,21 +27,62 @@ import java.util.Collections;
import java.util.List;
import org.apache.jena.graph.Node ;
+import org.apache.jena.sparql.modify.request.UpdateWithUsing;
+import org.apache.jena.update.Update;
+import org.apache.jena.update.UpdateException;
+import org.apache.jena.update.UpdateRequest;
public class UsingList
{
public UsingList() { }
-
+
private List<Node> using = new ArrayList<>() ;
private List<Node> usingNamed = new ArrayList<>() ;
-
+
public void addUsing(Node node) { using.add(node) ; }
public void addAllUsing(Collection<Node> nodes) {
using.addAll(nodes); }
public void addUsingNamed(Node node) {
usingNamed.add(node) ; }
public void addAllUsingNamed(Collection<Node> nodes) {
usingNamed.addAll(nodes); }
-
+
public List<Node> getUsing() { return
Collections.unmodifiableList(using) ; }
public List<Node> getUsingNamed() { return
Collections.unmodifiableList(usingNamed) ; }
-
+
public boolean usingIsPresent() { return using.size()
> 0 || usingNamed.size() > 0 ; }
+
+ /**
+ * This modifies the {@link Update Updates} of the {@link UpdateRequest}.
+ * The using list may come from the protocol so this
+ * operation converts a request+protocol into a
+ * self-contained UpdateRequest.
+ */
+ public static UpdateRequest modifyUpdateForUsingList(UpdateRequest
updateRequest, UsingList usingList) {
+ if ( usingList == null || ! usingList.usingIsPresent() )
+ return updateRequest;
+ UpdateRequest request = new UpdateRequest();
+ updateRequest.forEach(update->{
+ Update update2 = modifyUpdateForUsingList(update, usingList);
+ request.add(update2);
+ });
+ return request;
+ }
+
+ /**
+ * This modifies the {@link Update}.
+ * The using list may come from the protocol so this
+ * operation converts a request+protocol into a
+ * self-contained UpdateRequest.
+ */
+ public static Update modifyUpdateForUsingList(Update update, UsingList
usingList) {
+ if ( usingList == null || ! usingList.usingIsPresent() )
+ return update;
+ if ( ! ( update instanceof UpdateWithUsing upu ) )
+ return update;
+ if ( upu.getUsing().size() != 0 || upu.getUsingNamed().size() != 0 ||
upu.getWithIRI() != null )
+ throw new UpdateException("SPARQL Update: Protocol using-graph-uri
or using-named-graph-uri present where update request has USING, USING NAMED or
WITH");
+ for ( Node node : usingList.getUsing() )
+ upu.addUsing(node);
+ for ( Node node : usingList.getUsingNamed() )
+ upu.addUsingNamed(node);
+ return update;
+ }
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingUpdateSink.java
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingUpdateSink.java
index 10d246e4ac..eb122644a3 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingUpdateSink.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/modify/UsingUpdateSink.java
@@ -21,11 +21,8 @@
package org.apache.jena.sparql.modify;
-import org.apache.jena.graph.Node ;
import org.apache.jena.sparql.modify.request.QuadDataAccSink ;
-import org.apache.jena.sparql.modify.request.UpdateWithUsing ;
import org.apache.jena.update.Update ;
-import org.apache.jena.update.UpdateException ;
/**
* Adds using clauses from the UsingList to UpdateWithUsing operations; will
throw an
@@ -44,17 +41,8 @@ public class UsingUpdateSink implements UpdateSink {
public void send(Update update) {
// ---- check USING/USING NAMED/WITH not used.
// ---- update request to have USING/USING NAMED
- if ( null != usingList && usingList.usingIsPresent() ) {
- if ( update instanceof UpdateWithUsing ) {
- UpdateWithUsing upu = (UpdateWithUsing)update;
- if ( upu.getUsing().size() != 0 || upu.getUsingNamed().size()
!= 0 || upu.getWithIRI() != null )
- throw new UpdateException("SPARQL Update: Protocol
using-graph-uri or using-named-graph-uri present where update request has
USING, USING NAMED or WITH");
- for ( Node node : usingList.getUsing() )
- upu.addUsing(node);
- for ( Node node : usingList.getUsingNamed() )
- upu.addUsingNamed(node);
- }
- }
+ if ( null != usingList && usingList.usingIsPresent() )
+ update = UsingList.modifyUpdateForUsingList(update, usingList);
sink.send(update);
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateLoad.java
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateLoad.java
index 6fe76f852c..f973c60c04 100644
---
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateLoad.java
+++
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateLoad.java
@@ -35,7 +35,6 @@ public class UpdateLoad extends Update
private final Node dest;
private boolean silent;
-
public UpdateLoad(String source, String dest) {
this(source, NodeFactory.createURI(dest), false);
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateWithUsing.java
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateWithUsing.java
index 6b860c4f70..2fd709b51a 100644
---
a/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateWithUsing.java
+++
b/jena-arq/src/main/java/org/apache/jena/sparql/modify/request/UpdateWithUsing.java
@@ -30,6 +30,7 @@ import org.apache.jena.sparql.util.Iso ;
import org.apache.jena.sparql.util.NodeIsomorphismMap ;
import org.apache.jena.update.Update ;
+/** An update operation that can have USING/USING NAMED clauses */
public abstract class UpdateWithUsing extends Update
{
private Node withIRI = null ;
@@ -38,15 +39,15 @@ public abstract class UpdateWithUsing extends Update
private List<Node> usingView = Collections.unmodifiableList(using) ;
private List<Node> usingNamedView =
Collections.unmodifiableList(usingNamed) ;
-
+
public UpdateWithUsing() {}
public void addUsing(Node node) { using.add(node) ; }
public void addUsingNamed(Node node) { usingNamed.add(node) ; }
-
+
public List<Node> getUsing() { return usingView ; }
public List<Node> getUsingNamed() { return usingNamedView ; }
-
+
public Node getWithIRI() { return withIRI ; }
public void setWithIRI(Node node) { this.withIRI = node ; }
@@ -65,5 +66,5 @@ public abstract class UpdateWithUsing extends Update
if ( ! Iso.isomorphicNodes(usingNamed, other.usingNamed, isoMap) )
return false ;
return true ;
- }
+ }
}
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
b/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
index a6255a94d3..d497f04782 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
@@ -89,6 +89,8 @@ public class Context {
}
protected void mapPutAll(Context other) {
+ if ( other == null )
+ return;
if ( readonly )
throw new ARQException("Context is readonly");
other.mapForEach(context::put);
diff --git a/jena-arq/src/main/java/org/apache/jena/update/UpdateAction.java
b/jena-arq/src/main/java/org/apache/jena/update/UpdateAction.java
index 4eb441c4de..b45a2fdbe8 100644
--- a/jena-arq/src/main/java/org/apache/jena/update/UpdateAction.java
+++ b/jena-arq/src/main/java/org/apache/jena/update/UpdateAction.java
@@ -38,6 +38,7 @@ import org.apache.jena.sparql.modify.UpdateSink;
import org.apache.jena.sparql.modify.UsingList;
import org.apache.jena.sparql.modify.UsingUpdateSink;
import org.apache.jena.sparql.modify.request.UpdateWithUsing;
+import org.apache.jena.sparql.util.Context;
/**
* A class of forms for executing SPARQL Update operations. parse means the
update
@@ -83,7 +84,19 @@ public class UpdateAction {
*/
public static void readExecute(String filename, DatasetGraph dataset) {
UpdateRequest req = UpdateFactory.read(filename);
- execute$(req, dataset);
+ execute$(req, dataset, null);
+ }
+
+ /**
+ * Read a file containing SPARQL Update operations, and execute the
operations.
+ *
+ * @param filename
+ * @param dataset
+ * @param context
+ */
+ public static void readExecute(String filename, DatasetGraph dataset,
Context context) {
+ UpdateRequest req = UpdateFactory.read(filename);
+ execute$(req, dataset, context);
}
/**
@@ -168,7 +181,7 @@ public class UpdateAction {
* @param dataset
*/
public static void execute(UpdateRequest request, DatasetGraph dataset) {
- execute$(request, dataset);
+ execute$(request, dataset, null);
}
/**
@@ -177,9 +190,22 @@ public class UpdateAction {
* @param request
* @param dataset
* @param inputBinding
+ * @deprecated To be removed
*/
+ @Deprecated(forRemoval = true)
public static void execute(UpdateRequest request, Dataset dataset,
QuerySolution inputBinding) {
- execute$(request, dataset.asDatasetGraph());
+ execute$(request, dataset.asDatasetGraph(), null);
+ }
+
+ /**
+ * Execute SPARQL Update operations.
+ *
+ * @param request
+ * @param dataset
+ * @param context
+ */
+ public static void execute(UpdateRequest request, Dataset dataset, Context
context) {
+ execute$(request, dataset.asDatasetGraph(), context);
}
private static DatasetGraph toDatasetGraph(Graph graph) {
@@ -188,8 +214,8 @@ public class UpdateAction {
// All non-streaming updates come through here.
- private static void execute$(UpdateRequest request, DatasetGraph
datasetGraph) {
- UpdateExec uProc =
UpdateExec.newBuilder().update(request).dataset(datasetGraph).build();
+ private static void execute$(UpdateRequest request, DatasetGraph
datasetGraph, Context context) {
+ UpdateExec uProc =
UpdateExec.newBuilder().update(request).dataset(datasetGraph).context(context).build();
if ( uProc == null )
throw new ARQException("No suitable update procesors are
registered/able to execute your updates");
uProc.execute();
@@ -232,13 +258,24 @@ public class UpdateAction {
* @param dataset
*/
public static void execute(Update update, DatasetGraph dataset) {
- execute$(update, dataset);
+ execute$(update, dataset, null);
}
- private static void execute$(Update update, DatasetGraph datasetGraph) {
+ /**
+ * Execute a single SPARQL Update operation.
+ *
+ * @param update
+ * @param dataset
+ * @param context
+ */
+ public static void execute(Update update, DatasetGraph dataset, Context
context) {
+ execute$(update, dataset, context);
+ }
+
+ private static void execute$(Update update, DatasetGraph datasetGraph,
Context context) {
UpdateRequest request = new UpdateRequest();
request.add(update);
- execute$(request, datasetGraph);
+ execute$(request, datasetGraph, context);
}
// Streaming Updates:
@@ -306,16 +343,35 @@ public class UpdateAction {
/**
* Parse update operations into a DatasetGraph by parsing from an
InputStream.
*
- * @param usingList A list of USING or USING NAMED statements that be
added to
- * all {@link UpdateWithUsing} queries
+ * @param usingList A list of USING or USING NAMED statements that are
added to all {@link UpdateWithUsing} queries
* @param dataset The dataset to apply the changes to
* @param input The source of the update request (must be UTF-8).
* @param baseURI The base URI for resolving relative URIs (may be
* <code>null</code>)
* @param syntax The update language syntax
*/
+
public static void parseExecute(UsingList usingList, DatasetGraph dataset,
InputStream input, String baseURI, Syntax syntax) {
- UpdateProcessorStreaming uProc =
UpdateStreaming.createStreaming(dataset);
+ parseExecute$(usingList, dataset, input, baseURI, syntax, null);
+ }
+
+ /**
+ * Parse update operations into a DatasetGraph by parsing from an
InputStream.
+ *
+ * @param usingList A list of USING or USING NAMED statements that are
added to all {@link UpdateWithUsing} queries
+ * @param dataset The dataset to apply the changes to
+ * @param input The source of the update request (must be UTF-8).
+ * @param baseURI The base URI for resolving relative URIs (may be
+ * <code>null</code>)
+ * @param syntax The update language syntax
+ * @param context
+ */
+ public static void parseExecute(UsingList usingList, DatasetGraph dataset,
InputStream input, String baseURI, Syntax syntax, Context context) {
+ parseExecute$(usingList, dataset, input, baseURI, syntax, context);
+ }
+
+ private static void parseExecute$(UsingList usingList, DatasetGraph
dataset, InputStream input, String baseURI, Syntax syntax, Context context) {
+ UpdateProcessorStreaming uProc =
UpdateStreaming.createStreaming(dataset, context);
if ( uProc == null )
throw new ARQException("No suitable update procesors are
registered/able to execute your updates");
diff --git
a/jena-arq/src/test/java/org/apache/jena/sparql/modify/TestUpdateBuild.java
b/jena-arq/src/test/java/org/apache/jena/sparql/modify/TestUpdateBuild.java
index ebd8a2c504..3f93e0c95b 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/modify/TestUpdateBuild.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/modify/TestUpdateBuild.java
@@ -23,16 +23,21 @@ package org.apache.jena.sparql.modify;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.query.ARQ;
+import org.apache.jena.riot.system.streammgr.StreamManager;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.DatasetGraphFactory;
import org.apache.jena.sparql.engine.binding.Binding;
import org.apache.jena.sparql.exec.UpdateExec;
import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.sparql.util.Context;
+import org.apache.jena.update.UpdateException;
import org.apache.jena.update.UpdateFactory;
import org.apache.jena.update.UpdateRequest;
@@ -105,4 +110,18 @@ public class TestUpdateBuild {
});
assertEquals(2, Iter.count(dsg.find()));
}
+
+ @Test public void update_build_context_1() {
+ DatasetGraph dsg = DatasetGraphFactory.createTxnMem();
+ assertTrue(dsg.isEmpty());
+ Context context = ARQ.getContext().copy();
+ StreamManager streamManager = new StreamManager();
+ StreamManager.set(context, streamManager);
+ UpdateExec upExec = UpdateExec.newBuilder()
+ .context(context)
+ .dataset(dsg)
+ .update("LOAD <file:testing/Update/empty.nt>")
+ .build();
+ assertThrows(UpdateException.class, () -> dsg.execute(
()->upExec.execute() ));
+ }
}
diff --git
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
index 6b45d40f5a..43ae113084 100644
---
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
+++
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java
@@ -232,7 +232,7 @@ public class Fuseki {
public static final StreamManager webStreamManager;
static {
webStreamManager = new StreamManager();
- // Only know how to handle http URLs
+ // Only know how to handle http and ftp URLs
webStreamManager.addLocator(new LocatorHTTP());
webStreamManager.addLocator(new LocatorFTP());
}
diff --git
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
index 2b16b79652..d886d2fad3 100644
---
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
+++
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
@@ -35,7 +35,6 @@ import java.util.function.BiConsumer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-
import org.apache.jena.atlas.RuntimeIOException;
import org.apache.jena.atlas.io.IO;
import org.apache.jena.atlas.web.AcceptList;
@@ -50,7 +49,10 @@ import org.apache.jena.fuseki.system.FusekiNetLib;
import org.apache.jena.graph.Graph;
import org.apache.jena.http.HttpMethod;
import org.apache.jena.riot.*;
-import org.apache.jena.riot.system.*;
+import org.apache.jena.riot.system.ErrorHandler;
+import org.apache.jena.riot.system.ErrorHandlerFactory;
+import org.apache.jena.riot.system.StreamRDF;
+import org.apache.jena.riot.system.StreamRDFLib;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.shared.JenaException;
import org.apache.jena.sparql.core.DatasetGraph;
@@ -237,6 +239,7 @@ public class ActionLib {
RDFParser.create()
.errorHandler(errorHandler)
.source(input)
+ .context(action.getContext())
.lang(lang)
.base(base)
.parse(dest);
diff --git
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
index bd296c14d0..a8e479351f 100644
---
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
+++
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
@@ -51,6 +51,7 @@ import org.apache.jena.fuseki.system.ActionCategory;
import org.apache.jena.query.ReadWrite;
import org.apache.jena.query.TxnType;
import org.apache.jena.riot.WebContent;
+import org.apache.jena.riot.system.streammgr.StreamManager;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.sparql.SystemARQ;
import org.apache.jena.sparql.core.DatasetGraph;
@@ -128,11 +129,18 @@ public class HttpAction
this.log = log;
this.category = category;
this.request = request;
+
this.response = new HttpServletResponseTracker(this, response);
this.contextPath = request.getServletContext().getContextPath();
this.actionURI = ActionLib.actionURI(request);
this.serviceDispatchRegistry =
OperationRegistry.get(request.getServletContext());
this.dataAccessPointRegistry =
DataAccessPointRegistry.get(request.getServletContext());
+
+ // Per HttpAction isolated Context with StreamManager set to resolve
only URLs (http:, ftp:)
+ // This is the only copy needed. Manipulation of the context
+ // during the action execution can modify this copy.
+ this.context = Fuseki.getContext().copy();
+ StreamManager.set(context, Fuseki.webStreamManager);
}
/**
@@ -177,9 +185,9 @@ public class HttpAction
*/
private void setDataset(DatasetGraph dsg) {
this.dsg = dsg;
- this.context = Context.mergeCopy(Fuseki.getContext(),
dsg.getContext());
if ( dsg == null )
return;
+ context.putAll(dsg.getContext());
setTransactionalPolicy(dsg);
}
@@ -278,8 +286,8 @@ public class HttpAction
* @param endpoint {@link Endpoint}
*/
public void setEndpoint(Endpoint endpoint) {
- if ( endpoint != null )
- this.context = Context.mergeCopy(getContext(),
endpoint.getContext());
+ if ( endpoint != null && endpoint.getContext() != null )
+ context.putAll(endpoint.getContext());
this.endpoint = endpoint;
}
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
index e81d59fc63..73e4f7e466 100644
---
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
@@ -231,7 +231,8 @@ public class Responses
if ( ! ResultSetWriterRegistry.isRegistered(lang) )
ServletOps.errorBadRequest("No results writer for
"+serializationType);
- Context cxt = action.getContext().copy();
+ // No need to copy because each Action has it's own context which
is not shared.
+ Context cxt = action.getContext();
String charset = charsetUTF8;
String jsonCallback = null;
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 1cf1606a03..1d4ad238bc 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
@@ -145,7 +145,7 @@ public abstract class SPARQLQueryProcessor extends
ActionService
} catch (ActionErrorException ex) {
throw ex;
}
- // Query not yet parsed.
+ // Query has not yet been parsed.
}
/**
diff --git
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
index 1f316c0eaf..c8a3b2f6c3 100644
---
a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
+++
b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
@@ -58,6 +58,8 @@ import org.apache.jena.query.Syntax;
import org.apache.jena.riot.WebContent;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.shared.OperationDeniedException;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.exec.UpdateExec;
import org.apache.jena.sparql.modify.UsingList;
import org.apache.jena.update.UpdateAction;
import org.apache.jena.update.UpdateException;
@@ -213,18 +215,17 @@ public class SPARQL_Update extends ActionService
// If the dsg is transactional, then we can parse and execute the
update in a streaming fashion.
// If it isn't, we need to read the entire update request before
performing any updates, because
// we have to attempt to make the request atomic in the face of
malformed updates.
- UpdateRequest req = null;
+ UpdateRequest updateRequest = null;
// Using the request for the base URL exposes information about the
host,
// and the host may be behind a firewall, with the request going to a
proxy/gateway.
// The request URL is not the firewall public host name.
-
String requestBase = UpdateParseBase;
- // BAD: base = action.getRequest().getRequestURL().toString();
if (!action.isTransactional()) {
+ // No abort. We need to know the request is valid - parse it now.
try {
- req = UpdateFactory.read(usingList, input, requestBase,
Syntax.syntaxARQ);
+ updateRequest = UpdateFactory.read(usingList, input,
requestBase, Syntax.syntaxARQ);
}
catch (UpdateException ex) {
ServletOps.errorBadRequest(ex.getMessage()); return; }
catch (QueryParseException ex) {
ServletOps.errorBadRequest(messageForException(ex)); return; }
@@ -232,10 +233,15 @@ public class SPARQL_Update extends ActionService
action.beginWrite();
try {
- if (req == null )
- UpdateAction.parseExecute(usingList, action.getActiveDSG(),
input, requestBase, Syntax.syntaxARQ);
- else
- UpdateAction.execute(req, action.getActiveDSG());
+ if (updateRequest == null ) {
+ // streaming path.
+ UpdateAction.parseExecute(usingList, action.getActiveDSG(),
input, requestBase, Syntax.syntaxARQ, action.getContext());
+ }
+ else {
+ // Non-streaming path. The request has been parsed.
+ UpdateRequest updateRequest2 =
UsingList.modifyUpdateForUsingList(updateRequest, usingList);
+ executeRequest(action, updateRequest2, usingList,
action.getActiveDSG());
+ }
action.commit();
} catch (QueryParseException ex) {
ActionLib.consumeBody(action);
@@ -264,6 +270,15 @@ public class SPARQL_Update extends ActionService
} finally { action.endWrite(); }
}
+ private static void executeRequest(HttpAction action, UpdateRequest
request, UsingList usingList, DatasetGraph dsg) {
+ UpdateRequest request2 = UsingList.modifyUpdateForUsingList(request,
usingList);
+ UpdateExec.newBuilder()
+ .context(action.getContext())
+ .dataset(action.getActiveDSG())
+ .update(request2)
+ .execute();
+ }
+
/**
* Content type, with a default depending on whether it looks like a
HTMLform
* using the query string.
diff --git
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
index 09da890cda..5efd592975 100644
---
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
+++
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java
@@ -56,7 +56,7 @@ import org.apache.jena.fuseki.main.sys.TestFusekiModules;
, TestHttpOptions.class
, TestQuery.class
, TestSPARQLProtocol.class
- , TestUpdate.class
+ , TestSPARQLUpdate.class
, TestPatchFuseki.class
, TestFusekiCustomScriptFunc.class
diff --git
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestUpdate.java
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLUpdate.java
similarity index 60%
rename from
jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestUpdate.java
rename to
jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLUpdate.java
index 77506b2070..3c97ed2dfa 100644
---
a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestUpdate.java
+++
b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLUpdate.java
@@ -21,14 +21,21 @@
package org.apache.jena.fuseki.main;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Path;
+
import org.junit.jupiter.api.Test;
+import org.apache.jena.atlas.lib.FileOps;
import org.apache.jena.rdflink.RDFLink;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.sparql.exec.QueryExec;
import org.apache.jena.sparql.exec.UpdateExec;
-public class TestUpdate {
+public class TestSPARQLUpdate {
/*
curl -v -XPOST
'http://localhost:3030/'"${DS}"'/?using-named-graph-uri=http%3A%2F%2Fexample%2Fpeople'
\
-H 'Content-type: application/sparql-update' \
@@ -42,15 +49,18 @@ curl -v -XPOST
'http://localhost:3030/'"${DS}"'/?using-named-graph-uri=http%3A%2
private FusekiServer server() {
DatasetGraph dsgTesting = DatasetGraphFactory.createTxnMem();
+ return server(dsgTesting);
+ }
+
+ private FusekiServer server(DatasetGraph dsg) {
FusekiServer server = FusekiServer.create()
.port(0)
//.verbose(true)
- .add(DS, dsgTesting)
+ .add(DS, dsg)
.enablePing(true)
.enableMetrics(true)
.start();
return server;
-
}
@Test public void update2() {
@@ -75,4 +85,37 @@ curl -v -XPOST
'http://localhost:3030/'"${DS}"'/?using-named-graph-uri=http%3A%2
UpdateExec.service(URL).update(PREFIXES+" WITH
<http://example/ng2> INSERT { :s :p :o } WHERE {}").execute()
);
}
+
+ @Test public void updateLoadFile_1() {
+ FusekiServer server = server();
+ String serviceURL = server.datasetURL(DS);
+
+ // This will resolve to the same place in the test server.
+ String FN = "testing/Files/data.ttl";
+ String loadFile = Path.of(FN).toAbsolutePath().toString();
+ assertTrue(FileOps.exists(loadFile), "No test file");
+
+ FusekiTestLib.expect400(()-> {
+ UpdateExec.service(serviceURL).update("LOAD
<file:"+loadFile+">").execute(); });
+ boolean hasTriples= QueryExec.service(serviceURL).query("ASK { ?s ?p
?o }").ask();
+ assertFalse(hasTriples, "Dataset not empty");
+ }
+
+ @Test public void updateLoadFile_2() {
+ // No abort.
+ DatasetGraph dsgTesting = DatasetGraphFactory.createGeneral();
+ FusekiServer server = server(dsgTesting);
+ String serviceURL = server.datasetURL(DS);
+
+ // This will resolve to the same place in the test server.
+ String FN = "testing/Files/data.ttl";
+ String loadFile = Path.of(FN).toAbsolutePath().toString();
+ assertTrue(FileOps.exists(loadFile), "No test file");
+
+ FusekiTestLib.expect400(()-> {
+ UpdateExec.service(serviceURL).update("LOAD
<file:"+loadFile+">").execute(); });
+ boolean hasTriples= QueryExec.service(serviceURL).query("ASK { ?s ?p
?o }").ask();
+ assertFalse(hasTriples, "Dataset not empty");
+ }
+
}
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Files/data.ttl
b/jena-fuseki2/jena-fuseki-main/testing/Files/data.ttl
new file mode 100644
index 0000000000..8a9fda4469
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Files/data.ttl
@@ -0,0 +1,3 @@
+PREFIX : <http://example/>
+
+:s :p :o .