TransactionStartController.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_CREATED;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.rdf4j.common.transaction.IsolationLevel;
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
import org.eclipse.rdf4j.common.transaction.TransactionSetting;
import org.eclipse.rdf4j.common.transaction.TransactionSettingRegistry;
import org.eclipse.rdf4j.common.webapp.views.SimpleResponseView;
import org.eclipse.rdf4j.http.protocol.Protocol;
import org.eclipse.rdf4j.http.server.ClientHTTPException;
import org.eclipse.rdf4j.http.server.ProtocolUtil;
import org.eclipse.rdf4j.http.server.ServerHTTPException;
import org.eclipse.rdf4j.http.server.repository.RepositoryInterceptor;
import org.eclipse.rdf4j.model.vocabulary.SESAME;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
/**
* Handles requests for transaction creation on a repository.
*
* @author Jeen Broekstra
*/
public class TransactionStartController extends AbstractController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private String externalUrl;
public TransactionStartController() throws ApplicationContextException {
setSupportedMethods(METHOD_POST);
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
ModelAndView result;
Repository repository = RepositoryInterceptor.getRepository(request);
String reqMethod = request.getMethod();
if (METHOD_POST.equals(reqMethod)) {
logger.info("POST transaction start");
result = startTransaction(repository, request, response);
logger.info("transaction started");
} else {
throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
"Method not allowed: " + reqMethod);
}
return result;
}
@Deprecated
ArrayList<TransactionSetting> getIsolationLevel(HttpServletRequest request) {
// process legacy isolation level param for backward compatibility with older clients
ArrayList<TransactionSetting> transactionSettings = new ArrayList<>();
final String isolationLevelString = request.getParameter(Protocol.ISOLATION_LEVEL_PARAM_NAME);
if (isolationLevelString != null) {
final String isolationLevelConverted = isolationLevelString.replace(SESAME.NAMESPACE, "");
for (IsolationLevel standardLevel : IsolationLevels.values()) {
if (standardLevel.getValue().equals(isolationLevelConverted)) {
transactionSettings.add(IsolationLevels.valueOf(isolationLevelConverted));
break;
}
}
if (transactionSettings.isEmpty())
throw new IllegalArgumentException("Unknown isolation-level setting " + isolationLevelString);
}
return transactionSettings;
}
ArrayList<TransactionSetting> getTransactionSettings(HttpServletRequest request) {
ArrayList<TransactionSetting> transactionSettings = new ArrayList<>();
request.getParameterMap().forEach((k, v) -> {
if (k.startsWith(Protocol.TRANSACTION_SETTINGS_PREFIX)) {
String settingsName = k.replace(Protocol.TRANSACTION_SETTINGS_PREFIX, "");
// FIXME we should make the isolation level an SPI impl as well so that it will work with
// non-standard isolation levels
if (settingsName.equals(IsolationLevels.NONE.getName())) {
transactionSettings.add(IsolationLevels.valueOf(v[0]));
} else {
TransactionSettingRegistry.getInstance()
.get(settingsName)
.flatMap(factory -> factory.getTransactionSetting(v[0]))
.ifPresent(transactionSettings::add);
}
}
});
return transactionSettings;
}
Transaction createTransaction(Repository repository) throws ExecutionException, InterruptedException {
return new Transaction(repository);
}
private ModelAndView startTransaction(Repository repository, HttpServletRequest request,
HttpServletResponse response) throws IOException, ClientHTTPException, ServerHTTPException {
ProtocolUtil.logRequestParameters(request);
Map<String, Object> model = new HashMap<>();
ArrayList<TransactionSetting> transactionSettings = getIsolationLevel(request);
transactionSettings.addAll(getTransactionSettings(request));
Transaction txn = null;
boolean allGood = false;
try {
txn = createTransaction(repository);
if (transactionSettings.isEmpty()) {
txn.begin();
} else {
txn.begin(transactionSettings.toArray(new TransactionSetting[0]));
}
UUID txnId = txn.getID();
model.put(SimpleResponseView.SC_KEY, SC_CREATED);
final StringBuffer txnURL = getUrlBasePath(request);
txnURL.append("/" + txnId.toString());
Map<String, String> customHeaders = new HashMap<>();
customHeaders.put("Location", txnURL.toString());
model.put(SimpleResponseView.CUSTOM_HEADERS_KEY, customHeaders);
ModelAndView result = new ModelAndView(SimpleResponseView.getInstance(), model);
ActiveTransactionRegistry.INSTANCE.register(txn);
allGood = true;
return result;
} catch (RepositoryException | InterruptedException | ExecutionException e) {
throw new ServerHTTPException("Transaction start error: " + e.getMessage(), e);
} finally {
if (!allGood) {
try {
txn.close();
} catch (InterruptedException | ExecutionException e) {
throw new ServerHTTPException("Transaction start error: " + e.getMessage(), e);
}
}
}
}
private StringBuffer getUrlBasePath(final HttpServletRequest request) {
if (externalUrl == null) {
return request.getRequestURL();
}
final StringBuffer url = new StringBuffer();
if (externalUrl.endsWith("/")) {
url.append(externalUrl, 0, externalUrl.length() - 1);
} else {
url.append(externalUrl);
}
url.append(request.getRequestURI());
return url;
}
public void setExternalUrl(final String externalUrl) {
this.externalUrl = externalUrl;
}
}