TransactionController.java
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.http.server.repository.transaction;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_ACCEPTABLE;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import static org.eclipse.rdf4j.http.protocol.Protocol.BINDING_PREFIX;
import static org.eclipse.rdf4j.http.protocol.Protocol.CONTEXT_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.DEFAULT_GRAPH_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.INCLUDE_INFERRED_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.INSERT_GRAPH_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.NAMED_GRAPH_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.OBJECT_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.PREDICATE_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.QUERY_LANGUAGE_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.QUERY_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.REMOVE_GRAPH_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.SUBJECT_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.USING_GRAPH_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.USING_NAMED_GRAPH_PARAM_NAME;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.eclipse.rdf4j.common.lang.FileFormat;
import org.eclipse.rdf4j.common.lang.service.FileFormatServiceRegistry;
import org.eclipse.rdf4j.common.webapp.views.EmptySuccessView;
import org.eclipse.rdf4j.common.webapp.views.SimpleResponseView;
import org.eclipse.rdf4j.http.protocol.Protocol;
import org.eclipse.rdf4j.http.protocol.Protocol.Action;
import org.eclipse.rdf4j.http.protocol.error.ErrorInfo;
import org.eclipse.rdf4j.http.protocol.error.ErrorType;
import org.eclipse.rdf4j.http.server.ClientHTTPException;
import org.eclipse.rdf4j.http.server.HTTPException;
import org.eclipse.rdf4j.http.server.ProtocolUtil;
import org.eclipse.rdf4j.http.server.ServerHTTPException;
import org.eclipse.rdf4j.http.server.repository.BooleanQueryResultView;
import org.eclipse.rdf4j.http.server.repository.GraphQueryResultView;
import org.eclipse.rdf4j.http.server.repository.QueryResultView;
import org.eclipse.rdf4j.http.server.repository.RepositoryInterceptor;
import org.eclipse.rdf4j.http.server.repository.TupleQueryResultView;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.query.BooleanQuery;
import org.eclipse.rdf4j.query.GraphQuery;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.Query;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.QueryInterruptedException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.UnsupportedQueryLanguageException;
import org.eclipse.rdf4j.query.UpdateExecutionException;
import org.eclipse.rdf4j.query.impl.SimpleDataset;
import org.eclipse.rdf4j.query.resultio.BooleanQueryResultWriterRegistry;
import org.eclipse.rdf4j.query.resultio.TupleQueryResultWriterRegistry;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFWriterFactory;
import org.eclipse.rdf4j.rio.RDFWriterRegistry;
import org.eclipse.rdf4j.rio.Rio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContextException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.AbstractController;
/**
* Handles requests for transaction creation on a repository.
*
* @author Jeen Broekstra
*/
public class TransactionController extends AbstractController implements DisposableBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public TransactionController() throws ApplicationContextException {
setSupportedMethods(METHOD_POST, "PUT", "DELETE");
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
ModelAndView result;
String reqMethod = request.getMethod();
UUID transactionId = getTransactionID(request);
logger.debug("transaction id: {}", transactionId);
logger.debug("request content type: {}", request.getContentType());
Transaction transaction = ActiveTransactionRegistry.INSTANCE.getTransaction(transactionId);
if (transaction == null) {
logger.warn("could not find transaction for transaction id {}", transactionId);
throw new ClientHTTPException(SC_BAD_REQUEST,
"unable to find registered transaction for transaction id '" + transactionId + "'");
}
// if no action is specified in the request, it's a rollback (since it's
// the only txn operation that does not require the action parameter).
final String actionParam = request.getParameter(Protocol.ACTION_PARAM_NAME);
final Action action = actionParam != null ? Action.valueOf(actionParam) : Action.ROLLBACK;
switch (action) {
case QUERY:
// TODO SES-2238 note that we allow POST requests for backward
// compatibility reasons with earlier
// 2.8.x releases, even though according to the protocol spec only
// PUT is allowed.
if ("PUT".equals(reqMethod) || METHOD_POST.equals(reqMethod)) {
logger.info("{} txn query request", reqMethod);
result = processQuery(transaction, request, response);
logger.info("{} txn query request finished", reqMethod);
} else {
throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"Method not allowed: " + reqMethod);
}
break;
case GET:
if ("PUT".equals(reqMethod) || METHOD_POST.equals(reqMethod)) {
logger.info("{} txn get/export statements request", reqMethod);
result = getExportStatementsResult(transaction, request, response);
logger.info("{} txn get/export statements request finished", reqMethod);
} else {
throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"Method not allowed: " + reqMethod);
}
break;
case SIZE:
if ("PUT".equals(reqMethod) || METHOD_POST.equals(reqMethod)) {
logger.info("{} txn size request", reqMethod);
result = getSize(transaction, request, response);
logger.info("{} txn size request finished", reqMethod);
} else {
throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"Method not allowed: " + reqMethod);
}
break;
case PING:
String text = Long.toString(ActiveTransactionRegistry.INSTANCE.getTimeout(TimeUnit.MILLISECONDS));
Map<String, String> model = Collections.singletonMap(SimpleResponseView.CONTENT_KEY, text);
result = new ModelAndView(SimpleResponseView.getInstance(), model);
break;
default:
// TODO Action.ROLLBACK check is for backward compatibility with
// older 2.8.x releases only. It's not in the protocol spec.
if ("DELETE".equals(reqMethod)
|| (action.equals(Action.ROLLBACK) && ("PUT".equals(reqMethod) || METHOD_POST.equals(reqMethod)))) {
logger.info("transaction rollback");
try {
transaction.rollback();
} finally {
try {
transaction.close();
} finally {
ActiveTransactionRegistry.INSTANCE.deregister(transaction);
}
}
result = new ModelAndView(EmptySuccessView.getInstance());
logger.info("transaction rollback request finished.");
} else if ("PUT".equals(reqMethod) || METHOD_POST.equals(reqMethod)) {
// TODO filter for appropriate PUT operations
logger.info("{} txn operation", reqMethod);
result = processModificationOperation(transaction, action, request, response);
logger.info("PUT txn operation request finished.");
} else {
throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"Method not allowed: " + reqMethod);
}
break;
}
if (!(transaction.isClosed() || transaction.isComplete())) {
ActiveTransactionRegistry.INSTANCE.active(transaction);
}
return result;
}
private UUID getTransactionID(HttpServletRequest request) throws ClientHTTPException {
String pathInfoStr = request.getPathInfo();
UUID txnID = null;
if (pathInfoStr != null && !pathInfoStr.equals("/")) {
String[] pathInfo = pathInfoStr.substring(1).split("/");
// should be of the form: /<Repository>/transactions/<txnID>
if (pathInfo.length == 3) {
try {
txnID = UUID.fromString(pathInfo[2]);
logger.debug("txnID is '{}'", txnID);
} catch (IllegalArgumentException e) {
throw new ClientHTTPException(SC_BAD_REQUEST, "not a valid transaction id: " + pathInfo[2]);
}
} else {
logger.warn("could not determine transaction id from path info {} ", pathInfoStr);
}
}
return txnID;
}
private ModelAndView processModificationOperation(Transaction transaction, Action action,
HttpServletRequest request, HttpServletResponse response) throws IOException, HTTPException {
ProtocolUtil.logRequestParameters(request);
Map<String, Object> model = new HashMap<>();
String baseURI = request.getParameter(Protocol.BASEURI_PARAM_NAME);
if (baseURI == null) {
baseURI = "";
}
final Resource[] contexts = ProtocolUtil.parseContextParam(request, CONTEXT_PARAM_NAME,
SimpleValueFactory.getInstance());
final boolean preserveNodeIds = ProtocolUtil.parseBooleanParam(request, Protocol.PRESERVE_BNODE_ID_PARAM_NAME,
false);
try {
RDFFormat format;
switch (action) {
case ADD:
format = Rio.getParserFormatForMIMEType(request.getContentType())
.orElseThrow(Rio.unsupportedFormat(request.getContentType()));
transaction.add(request.getInputStream(), baseURI, format, preserveNodeIds, contexts);
break;
case DELETE:
format = Rio.getParserFormatForMIMEType(request.getContentType())
.orElseThrow(Rio.unsupportedFormat(request.getContentType()));
transaction.delete(format, request.getInputStream(), baseURI);
break;
case UPDATE:
return getSparqlUpdateResult(transaction, request, response);
case PREPARE:
transaction.prepare();
break;
case COMMIT:
transaction.commit();
// If commit fails with an exception, deregister should be skipped so the user
// has a chance to do a proper rollback. See #725.
ActiveTransactionRegistry.INSTANCE.deregister(transaction);
break;
default:
logger.warn("transaction modification action '{}' not recognized", action);
throw new ClientHTTPException("modification action not recognized: " + action);
}
model.put(SimpleResponseView.SC_KEY, HttpServletResponse.SC_OK);
return new ModelAndView(SimpleResponseView.getInstance(), model);
} catch (Exception e) {
if (e instanceof ClientHTTPException) {
throw (ClientHTTPException) e;
} else {
throw new ServerHTTPException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Transaction handling error: " + e.getMessage(), e);
}
}
}
private ModelAndView getSize(Transaction transaction, HttpServletRequest request, HttpServletResponse response)
throws HTTPException {
ProtocolUtil.logRequestParameters(request);
Map<String, Object> model = new HashMap<>();
final boolean headersOnly = METHOD_HEAD.equals(request.getMethod());
if (!headersOnly) {
Repository repository = RepositoryInterceptor.getRepository(request);
ValueFactory vf = repository.getValueFactory();
Resource[] contexts = ProtocolUtil.parseContextParam(request, Protocol.CONTEXT_PARAM_NAME, vf);
long size;
try {
size = transaction.getSize(contexts);
} catch (RepositoryException | InterruptedException | ExecutionException e) {
throw new ServerHTTPException("Repository error: " + e.getMessage(), e);
}
model.put(SimpleResponseView.CONTENT_KEY, String.valueOf(size));
}
return new ModelAndView(SimpleResponseView.getInstance(), model);
}
/**
* Get all statements and export them as RDF.
*
* @return a model and view for exporting the statements.
*/
private ModelAndView getExportStatementsResult(Transaction transaction, HttpServletRequest request,
HttpServletResponse response) throws ClientHTTPException {
ProtocolUtil.logRequestParameters(request);
ValueFactory vf = SimpleValueFactory.getInstance();
Resource subj = ProtocolUtil.parseResourceParam(request, SUBJECT_PARAM_NAME, vf);
IRI pred = ProtocolUtil.parseURIParam(request, PREDICATE_PARAM_NAME, vf);
Value obj = ProtocolUtil.parseValueParam(request, OBJECT_PARAM_NAME, vf);
Resource[] contexts = ProtocolUtil.parseContextParam(request, CONTEXT_PARAM_NAME, vf);
boolean useInferencing = ProtocolUtil.parseBooleanParam(request, INCLUDE_INFERRED_PARAM_NAME, true);
RDFWriterFactory rdfWriterFactory = ProtocolUtil.getAcceptableService(request, response,
RDFWriterRegistry.getInstance());
Map<String, Object> model = new HashMap<>();
model.put(TransactionExportStatementsView.SUBJECT_KEY, subj);
model.put(TransactionExportStatementsView.PREDICATE_KEY, pred);
model.put(TransactionExportStatementsView.OBJECT_KEY, obj);
model.put(TransactionExportStatementsView.CONTEXTS_KEY, contexts);
model.put(TransactionExportStatementsView.USE_INFERENCING_KEY, Boolean.valueOf(useInferencing));
model.put(TransactionExportStatementsView.FACTORY_KEY, rdfWriterFactory);
model.put(TransactionExportStatementsView.HEADERS_ONLY, METHOD_HEAD.equals(request.getMethod()));
model.put(TransactionExportStatementsView.TRANSACTION_KEY, transaction);
return new ModelAndView(TransactionExportStatementsView.getInstance(), model);
}
/**
* Evaluates a query on the given connection and returns the resulting {@link QueryResultView}. The
* {@link QueryResultView} will take care of correctly releasing the connection back to the
* {@link ActiveTransactionRegistry}, after fully rendering the query result for sending over the wire.
*/
private ModelAndView processQuery(Transaction txn, HttpServletRequest request, HttpServletResponse response)
throws IOException, HTTPException {
String queryStr;
final String contentType = request.getContentType();
if (contentType != null && contentType.contains(Protocol.SPARQL_QUERY_MIME_TYPE)) {
Charset charset = getCharset(request);
queryStr = IOUtils.toString(request.getInputStream(), charset);
} else {
queryStr = request.getParameter(QUERY_PARAM_NAME);
}
View view;
Object queryResult;
FileFormatServiceRegistry<? extends FileFormat, ?> registry;
try {
Query query = getQuery(txn, queryStr, request, response);
if (query instanceof TupleQuery) {
TupleQuery tQuery = (TupleQuery) query;
queryResult = txn.evaluate(tQuery);
registry = TupleQueryResultWriterRegistry.getInstance();
view = TupleQueryResultView.getInstance();
} else if (query instanceof GraphQuery) {
GraphQuery gQuery = (GraphQuery) query;
queryResult = txn.evaluate(gQuery);
registry = RDFWriterRegistry.getInstance();
view = GraphQueryResultView.getInstance();
} else if (query instanceof BooleanQuery) {
BooleanQuery bQuery = (BooleanQuery) query;
queryResult = txn.evaluate(bQuery);
registry = BooleanQueryResultWriterRegistry.getInstance();
view = BooleanQueryResultView.getInstance();
} else {
throw new ClientHTTPException(SC_BAD_REQUEST, "Unsupported query type: " + query.getClass().getName());
}
} catch (QueryInterruptedException | InterruptedException | ExecutionException e) {
if (e.getCause() != null && e.getCause() instanceof MalformedQueryException) {
ErrorInfo errInfo = new ErrorInfo(ErrorType.MALFORMED_QUERY, e.getCause().getMessage());
throw new ClientHTTPException(SC_BAD_REQUEST, errInfo.toString());
} else {
logger.info("Query interrupted", e);
throw new ServerHTTPException(SC_SERVICE_UNAVAILABLE, "Query execution interrupted");
}
} catch (QueryEvaluationException e) {
logger.info("Query evaluation error", e);
if (e.getCause() != null && e.getCause() instanceof HTTPException) {
// custom signal from the backend, throw as HTTPException
// directly (see SES-1016).
throw (HTTPException) e.getCause();
} else {
throw new ServerHTTPException("Query evaluation error: " + e.getMessage());
}
}
Object factory = ProtocolUtil.getAcceptableService(request, response, registry);
Map<String, Object> model = new HashMap<>();
model.put(QueryResultView.FILENAME_HINT_KEY, "query-result");
model.put(QueryResultView.QUERY_RESULT_KEY, queryResult);
model.put(QueryResultView.FACTORY_KEY, factory);
model.put(QueryResultView.HEADERS_ONLY, false); // TODO needed for HEAD
// requests.
return new ModelAndView(view, model);
}
private static Charset getCharset(HttpServletRequest request) {
return request.getCharacterEncoding() != null ? Charset.forName(request.getCharacterEncoding())
: StandardCharsets.UTF_8;
}
private Query getQuery(Transaction txn, String queryStr, HttpServletRequest request, HttpServletResponse response)
throws IOException, ClientHTTPException, InterruptedException, ExecutionException {
Query result = null;
// default query language is SPARQL
QueryLanguage queryLn = QueryLanguage.SPARQL;
String queryLnStr = request.getParameter(QUERY_LANGUAGE_PARAM_NAME);
logger.debug("query language param = {}", queryLnStr);
if (queryLnStr != null) {
queryLn = QueryLanguage.valueOf(queryLnStr);
if (queryLn == null) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Unknown query language: " + queryLnStr);
}
}
String baseURI = request.getParameter(Protocol.BASEURI_PARAM_NAME);
// determine if inferred triples should be included in query evaluation
boolean includeInferred = ProtocolUtil.parseBooleanParam(request, INCLUDE_INFERRED_PARAM_NAME, true);
String timeout = request.getParameter(Protocol.TIMEOUT_PARAM_NAME);
int maxQueryTime = 0;
if (timeout != null) {
try {
maxQueryTime = Integer.parseInt(timeout);
} catch (NumberFormatException e) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Invalid timeout value: " + timeout);
}
}
// build a dataset, if specified
String[] defaultGraphURIs = request.getParameterValues(DEFAULT_GRAPH_PARAM_NAME);
String[] namedGraphURIs = request.getParameterValues(NAMED_GRAPH_PARAM_NAME);
SimpleDataset dataset = null;
if (defaultGraphURIs != null || namedGraphURIs != null) {
dataset = new SimpleDataset();
if (defaultGraphURIs != null) {
for (String defaultGraphURI : defaultGraphURIs) {
try {
IRI uri = null;
if (!"null".equals(defaultGraphURI)) {
uri = SimpleValueFactory.getInstance().createIRI(defaultGraphURI);
}
dataset.addDefaultGraph(uri);
} catch (IllegalArgumentException e) {
throw new ClientHTTPException(SC_BAD_REQUEST,
"Illegal URI for default graph: " + defaultGraphURI);
}
}
}
if (namedGraphURIs != null) {
for (String namedGraphURI : namedGraphURIs) {
try {
IRI uri = null;
if (!"null".equals(namedGraphURI)) {
uri = SimpleValueFactory.getInstance().createIRI(namedGraphURI);
}
dataset.addNamedGraph(uri);
} catch (IllegalArgumentException e) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Illegal URI for named graph: " + namedGraphURI);
}
}
}
}
try {
result = txn.prepareQuery(queryLn, queryStr, baseURI);
result.setIncludeInferred(includeInferred);
if (maxQueryTime > 0) {
result.setMaxExecutionTime(maxQueryTime);
}
if (dataset != null) {
result.setDataset(dataset);
}
// determine if any variable bindings have been set on this query.
@SuppressWarnings("unchecked")
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String parameterName = parameterNames.nextElement();
if (parameterName.startsWith(BINDING_PREFIX) && parameterName.length() > BINDING_PREFIX.length()) {
String bindingName = parameterName.substring(BINDING_PREFIX.length());
Value bindingValue = ProtocolUtil.parseValueParam(request, parameterName,
SimpleValueFactory.getInstance());
result.setBinding(bindingName, bindingValue);
}
}
} catch (UnsupportedQueryLanguageException e) {
ErrorInfo errInfo = new ErrorInfo(ErrorType.UNSUPPORTED_QUERY_LANGUAGE, queryLn.getName());
throw new ClientHTTPException(SC_BAD_REQUEST, errInfo.toString());
} catch (MalformedQueryException e) {
ErrorInfo errInfo = new ErrorInfo(ErrorType.MALFORMED_QUERY, e.getMessage());
throw new ClientHTTPException(SC_BAD_REQUEST, errInfo.toString());
} catch (RepositoryException e) {
logger.error("Repository error", e);
response.sendError(SC_INTERNAL_SERVER_ERROR);
}
return result;
}
private ModelAndView getSparqlUpdateResult(Transaction transaction, HttpServletRequest request,
HttpServletResponse response) throws ServerHTTPException, ClientHTTPException, HTTPException {
String sparqlUpdateString;
final String contentType = request.getContentType();
if (contentType != null && contentType.contains(Protocol.SPARQL_UPDATE_MIME_TYPE)) {
try {
Charset charset = getCharset(request);
sparqlUpdateString = IOUtils.toString(request.getInputStream(), charset);
} catch (IOException e) {
logger.warn("error reading sparql update string from request body", e);
throw new ClientHTTPException(SC_BAD_REQUEST,
"could not read SPARQL update string from body: " + e.getMessage());
}
} else {
sparqlUpdateString = request.getParameter(Protocol.UPDATE_PARAM_NAME);
}
if (null == sparqlUpdateString) {
throw new ClientHTTPException(SC_NOT_ACCEPTABLE, "Could not read SPARQL update string from body.");
}
logger.debug("SPARQL update string: {}", sparqlUpdateString);
// default query language is SPARQL
QueryLanguage queryLn = QueryLanguage.SPARQL;
String queryLnStr = request.getParameter(QUERY_LANGUAGE_PARAM_NAME);
logger.debug("query language param = {}", queryLnStr);
if (queryLnStr != null) {
queryLn = QueryLanguage.valueOf(queryLnStr);
if (queryLn == null) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Unknown query language: " + queryLnStr);
}
}
String baseURI = request.getParameter(Protocol.BASEURI_PARAM_NAME);
// determine if inferred triples should be included in query evaluation
boolean includeInferred = ProtocolUtil.parseBooleanParam(request, INCLUDE_INFERRED_PARAM_NAME, true);
// build a dataset, if specified
String[] defaultRemoveGraphURIs = request.getParameterValues(REMOVE_GRAPH_PARAM_NAME);
String[] defaultInsertGraphURIs = request.getParameterValues(INSERT_GRAPH_PARAM_NAME);
String[] defaultGraphURIs = request.getParameterValues(USING_GRAPH_PARAM_NAME);
String[] namedGraphURIs = request.getParameterValues(USING_NAMED_GRAPH_PARAM_NAME);
SimpleDataset dataset = new SimpleDataset();
if (defaultRemoveGraphURIs != null) {
for (String graphURI : defaultRemoveGraphURIs) {
try {
IRI uri = null;
if (!"null".equals(graphURI)) {
uri = SimpleValueFactory.getInstance().createIRI(graphURI);
}
dataset.addDefaultRemoveGraph(uri);
} catch (IllegalArgumentException e) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Illegal URI for default remove graph: " + graphURI);
}
}
}
if (defaultInsertGraphURIs != null && defaultInsertGraphURIs.length > 0) {
String graphURI = defaultInsertGraphURIs[0];
try {
IRI uri = null;
if (!"null".equals(graphURI)) {
uri = SimpleValueFactory.getInstance().createIRI(graphURI);
}
dataset.setDefaultInsertGraph(uri);
} catch (IllegalArgumentException e) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Illegal URI for default insert graph: " + graphURI);
}
}
if (defaultGraphURIs != null) {
for (String defaultGraphURI : defaultGraphURIs) {
try {
IRI uri = null;
if (!"null".equals(defaultGraphURI)) {
uri = SimpleValueFactory.getInstance().createIRI(defaultGraphURI);
}
dataset.addDefaultGraph(uri);
} catch (IllegalArgumentException e) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Illegal URI for default graph: " + defaultGraphURI);
}
}
}
if (namedGraphURIs != null) {
for (String namedGraphURI : namedGraphURIs) {
try {
IRI uri = null;
if (!"null".equals(namedGraphURI)) {
uri = SimpleValueFactory.getInstance().createIRI(namedGraphURI);
}
dataset.addNamedGraph(uri);
} catch (IllegalArgumentException e) {
throw new ClientHTTPException(SC_BAD_REQUEST, "Illegal URI for named graph: " + namedGraphURI);
}
}
}
if (logger.isDebugEnabled()) {
StringBuilder datasetStr = new StringBuilder();
dataset.getDefaultGraphs()
.forEach(g -> datasetStr.append("DEFAULT GRAPH: FROM <").append(g.stringValue()).append(">\n"));
dataset.getNamedGraphs()
.forEach(g -> datasetStr.append("NAMED GRAPH: FROM NAMED <").append(g.stringValue()).append(">\n"));
dataset.getDefaultRemoveGraphs()
.forEach(g -> datasetStr.append("DEFAULT REMOVE GRAPH: DELETE FROM <")
.append(g.stringValue())
.append(">\n"));
Optional.ofNullable(dataset.getDefaultInsertGraph())
.ifPresent(g -> datasetStr.append("DEFAULT INSERT GRAPH: INSERT INTO <")
.append(g.stringValue())
.append(">\n"));
logger.debug("Dataset: {}", datasetStr);
}
try {
// determine if any variable bindings have been set on this update.
@SuppressWarnings("unchecked")
Enumeration<String> parameterNames = request.getParameterNames();
Map<String, Value> bindings = new HashMap<>();
while (parameterNames.hasMoreElements()) {
String parameterName = parameterNames.nextElement();
if (parameterName.startsWith(BINDING_PREFIX) && parameterName.length() > BINDING_PREFIX.length()) {
String bindingName = parameterName.substring(BINDING_PREFIX.length());
Value bindingValue = ProtocolUtil.parseValueParam(request, parameterName,
SimpleValueFactory.getInstance());
bindings.put(bindingName, bindingValue);
}
}
transaction.executeUpdate(queryLn, sparqlUpdateString, baseURI, includeInferred, dataset, bindings);
return new ModelAndView(EmptySuccessView.getInstance());
} catch (UpdateExecutionException | InterruptedException | ExecutionException | RepositoryException e) {
if (e.getCause() != null && e.getCause() instanceof HTTPException) {
// custom signal from the backend, throw as HTTPException directly
// (see SES-1016).
throw (HTTPException) e.getCause();
} else {
throw new ServerHTTPException("Repository update error: " + e.getMessage(), e);
}
}
// custom signal from the backend, throw as HTTPException directly
// (see SES-1016).
catch (MalformedQueryException e) {
ErrorInfo errInfo = new ErrorInfo(ErrorType.MALFORMED_QUERY, e.getMessage());
throw new ClientHTTPException(SC_BAD_REQUEST, errInfo.toString());
}
}
// Comes from disposableBean interface so to be able to stop the ActiveTransactionRegistry scheduler
@Override
public void destroy()
throws Exception {
ActiveTransactionRegistry.INSTANCE.destroyScheduler();
}
}