Skip to content

Commit

Permalink
GH-4492 split into separate controllers for each action
Browse files Browse the repository at this point in the history
  • Loading branch information
abrokenjester committed Apr 16, 2023
1 parent be4bcd5 commit ad1f46e
Show file tree
Hide file tree
Showing 12 changed files with 1,067 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*******************************************************************************
* 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 org.eclipse.rdf4j.http.protocol.Protocol.CONTEXT_PARAM_NAME;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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.model.Resource;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.rio.RDFFormat;
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.mvc.AbstractController;

/**
* Handles requests for transaction creation on a repository.
*
* @author Jeen Broekstra
*/
public abstract class AbstractActionController extends AbstractController implements DisposableBean {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

public AbstractActionController() throws ApplicationContextException {
setSupportedMethods(new String[] { METHOD_POST, "PUT" });
}

@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
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 + "'");
}

try {
var result = handleAction(request, response, transaction);
if (!(transaction.isClosed() || transaction.isComplete())) {
ActiveTransactionRegistry.INSTANCE.active(transaction);
}

return result;
} catch (Exception e) {
if (e instanceof ClientHTTPException) {
throw (ClientHTTPException) e;
} else if (e instanceof ServerHTTPException) {
throw (ServerHTTPException) e;
} else {
throw new ServerHTTPException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Transaction handling error: " + e.getMessage(), e);
}
}

}

/**
* Handle the specific action as part of the supplied {@link Transaction} object.
*
* @param request the request
* @param transaction the transaction on which the action is to be executed
* @return result of the action (may not be null)
*/
protected abstract ModelAndView handleAction(HttpServletRequest request, HttpServletResponse response,
Transaction transaction) throws Exception;

/* private methods */

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;
}

static RDFFormat getRDFFormat(HttpServletRequest request) {
return Rio.getParserFormatForMIMEType(request.getContentType())
.orElseThrow(Rio.unsupportedFormat(request.getContentType()));
}

static String getBaseURI(HttpServletRequest request) {
String baseURI = request.getParameter(Protocol.BASEURI_PARAM_NAME);
return baseURI == null ? "" : baseURI;
}

static Resource[] getContexts(HttpServletRequest request) throws ClientHTTPException {
return ProtocolUtil.parseContextParam(request, CONTEXT_PARAM_NAME, SimpleValueFactory.getInstance());
}

static Charset getCharset(HttpServletRequest request) {
return request.getCharacterEncoding() != null ? Charset.forName(request.getCharacterEncoding())
: StandardCharsets.UTF_8;
}

/**
* A {@link ModelAndView} for a 200 OK response with an empty body
*/
static ModelAndView emptyOkResponse() {
Map<String, Object> model = new HashMap<>();
model.put(SimpleResponseView.SC_KEY, HttpServletResponse.SC_OK);
return new ModelAndView(SimpleResponseView.getInstance(), model);
}

// Comes from disposableBean interface so to be able to stop the ActiveTransactionRegistry scheduler
@Override
public void destroy() throws Exception {
ActiveTransactionRegistry.INSTANCE.destroyScheduler();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* 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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.rdf4j.http.protocol.Protocol;
import org.eclipse.rdf4j.http.server.ProtocolUtil;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
import org.springframework.web.servlet.ModelAndView;

/**
* @author jeen
*
*/
public class AddController extends AbstractActionController {

@Override
protected ModelAndView handleAction(HttpServletRequest request, HttpServletResponse response,
Transaction transaction) throws Exception {

var baseURI = getBaseURI(request);
var contexts = getContexts(request);

boolean preserveNodeIds = ProtocolUtil.parseBooleanParam(request, Protocol.PRESERVE_BNODE_ID_PARAM_NAME, false);
RDFFormat format = Rio.getParserFormatForMIMEType(request.getContentType())
.orElseThrow(Rio.unsupportedFormat(request.getContentType()));

transaction.add(request.getInputStream(), baseURI, format, preserveNodeIds, contexts);

return emptyOkResponse();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* 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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

/**
* @author jeen
*
*/
public class CommitController extends AbstractActionController {

@Override
protected ModelAndView handleAction(HttpServletRequest request, HttpServletResponse response,
Transaction transaction) throws Exception {

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);

return emptyOkResponse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* 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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

/**
* @author jeen
*
*/
public class DeleteController extends AbstractActionController {

@Override
protected ModelAndView handleAction(HttpServletRequest request, HttpServletResponse response,
Transaction transaction) throws Exception {

var baseURI = getBaseURI(request);
var format = getRDFFormat(request);

transaction.delete(format, request.getInputStream(), baseURI);

return emptyOkResponse();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* 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 org.eclipse.rdf4j.http.protocol.Protocol.CONTEXT_PARAM_NAME;
import static org.eclipse.rdf4j.http.protocol.Protocol.INCLUDE_INFERRED_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.SUBJECT_PARAM_NAME;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.rdf4j.http.server.ClientHTTPException;
import org.eclipse.rdf4j.http.server.ProtocolUtil;
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.rio.RDFWriterFactory;
import org.eclipse.rdf4j.rio.RDFWriterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;

/**
* @author jeen
*
*/
public class ExportController extends AbstractActionController {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
protected ModelAndView handleAction(HttpServletRequest request, HttpServletResponse response,
Transaction transaction) throws Exception {
logger.info("{} txn get/export statements request", request.getMethod());
var result = getExportStatementsResult(transaction, request, response);
logger.info("{} txn get/export statements request finished", request.getMethod());
return result;
}

/**
* 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);
}
}
Loading

0 comments on commit ad1f46e

Please sign in to comment.