io.telicent.core.SPARQL_Update_CQRS Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scg-system Show documentation
Show all versions of scg-system Show documentation
System code - plugins, extensions, entrypoints etc. - for Smart Cache Graph
/*
* Copyright (c) Telicent Ltd.
*
* Licensed 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 io.telicent.core;
import static java.lang.String.format;
import static org.apache.jena.fuseki.server.CounterName.UpdateExecErrors;
import static org.apache.jena.fuseki.servlets.ActionExecLib.incCounter;
import static org.apache.jena.fuseki.servlets.SPARQLProtocol.messageForException;
import static org.apache.jena.fuseki.servlets.SPARQLProtocol.messageForParseException;
import static org.apache.jena.riot.web.HttpNames.paramUsingGraphURI;
import static org.apache.jena.riot.web.HttpNames.paramUsingNamedGraphURI;
import java.io.InputStream;
import java.util.function.Consumer;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.jena.fuseki.Fuseki;
import org.apache.jena.fuseki.servlets.*;
import org.apache.jena.irix.IRIxResolver;
import org.apache.jena.query.QueryBuildException;
import org.apache.jena.query.QueryParseException;
import org.apache.jena.query.Syntax;
import org.apache.jena.shared.OperationDeniedException;
import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
import org.apache.jena.sparql.modify.UsingList;
import org.apache.jena.update.UpdateAction;
import org.apache.jena.update.UpdateException;
import org.apache.kafka.clients.producer.Producer;
/**
* Intended to extend SPARQL_Update
* Currently copies SPARQL_Update.exec
* Pure CQRS.
*/
public class SPARQL_Update_CQRS extends SPARQL_Update {
private static final String UpdateParseBase = Fuseki.BaseParserSPARQL;
private static final IRIxResolver resolver = IRIxResolver.create()
.base(UpdateParseBase)
.resolve(true)
.allowRelative(false)
.build();
private final String topic;
private final Producer producer;
private final Consumer onBegin;
private final Consumer onCommit;
private final Consumer onAbort;
public SPARQL_Update_CQRS(String topic,
Producer producer,
Consumer onBegin,
Consumer onCommit,
Consumer onAbort) {
super();
this.topic = topic;
this.producer = producer;
this.onBegin = onBegin;
this.onCommit = onCommit;
this.onAbort = onAbort;
}
// This can be a read-end action because the base dataset is only ever read.
// In CQRS.setup, there is a buffering dataset that stores the mutations.
private static final boolean executeAsWrite = true;
@Override
protected void execute(HttpAction action, InputStream input) {
UsingList usingList = processProtocol(action.getRequest());
if ( executeAsWrite )
action.beginWrite();
else
// Need BufferingdDatasetGarph to report "write txn" even if base DSG is read.
action.beginRead();
try {
CQRS.UpdateCQRS updateCtl = CQRS.startOperation(topic, producer, action, onBegin, onCommit, onAbort);
UpdateAction.parseExecute(usingList, updateCtl.dataset(), input, UpdateParseBase, Syntax.syntaxARQ);
// Don't make the changes until read back from Kafka.
CQRS.finishOperation(action, updateCtl);
if ( executeAsWrite )
action.abort();
// Finished with this - it might have a large buffering dataset so clearly release it.
updateCtl = null;
/* ---- */
} catch (UpdateException ex) {
ActionLib.consumeBody(action);
abortSilent(action);
incCounter(action.getEndpoint().getCounters(), UpdateExecErrors);
ServletOps.errorOccurred(ex.getMessage());
} catch (QueryParseException ex) {
ActionLib.consumeBody(action);
abortSilent(action);
String msg = messageForParseException(ex);
action.log.warn(format("[%d] Parse error: %s", action.id, msg));
ServletOps.errorBadRequest(messageForException(ex));
} catch (QueryBuildException|QueryExceptionHTTP ex) {
ActionLib.consumeBody(action);
abortSilent(action);
// Counter inc'ed further out.
String msg = messageForException(ex);
action.log.warn(format("[%d] Bad request: %s", action.id, msg));
ServletOps.errorBadRequest(messageForException(ex));
} catch (OperationDeniedException ex) {
ActionLib.consumeBody(action);
abortSilent(action);
throw ex;
} catch (Throwable ex) {
ActionLib.consumeBody(action);
if ( ! ( ex instanceof ActionErrorException ) ) {
abortSilent(action);
ServletOps.errorOccurred(ex.getMessage(), ex);
}
} finally { action.end(); }
}
//Necessary copies due to private.
/* [It is an error to supply the using-graph-uri or using-named-graph-uri parameters
* when using this protocol to convey a SPARQL 1.1 Update request that contains an
* operation that uses the USING, USING NAMED, or WITH clause.]
*
* We will simply capture any using parameters here and pass them to the parser, which will be
* responsible for throwing an UpdateException if the query violates the above requirement,
* and will also be responsible for adding the using parameters to update queries that can
* accept them.
*/
private UsingList processProtocol(HttpServletRequest request) {
String[] usingArgs = request.getParameterValues(paramUsingGraphURI);
String[] usingNamedArgs = request.getParameterValues(paramUsingNamedGraphURI);
if ( usingArgs == null && usingNamedArgs == null )
return null;
ServletOps.errorBadRequest("Not allowed: using-graph-uri or using-named-graph-uri parameters");
return null;
}
private static void abortSilent(HttpAction action) {
action.abortSilent();
}
}