org.openrdf.http.server.repository.transaction.TransactionController.java Source code

Java tutorial

Introduction

Here is the source code for org.openrdf.http.server.repository.transaction.TransactionController.java

Source

/* 
 * Licensed to Aduna under one or more contributor license agreements.  
 * See the NOTICE.txt file distributed with this work for additional 
 * information regarding copyright ownership. 
 *
 * Aduna licenses this file to you under the terms of the Aduna BSD 
 * License (the "License"); you may not use this file except in compliance 
 * with the License. See the LICENSE.txt file distributed with this work 
 * for the full License.
 *
 * 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 org.openrdf.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_SERVICE_UNAVAILABLE;
import static org.openrdf.http.protocol.Protocol.BINDING_PREFIX;
import static org.openrdf.http.protocol.Protocol.CONTEXT_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.DEFAULT_GRAPH_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.INCLUDE_INFERRED_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.INSERT_GRAPH_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.NAMED_GRAPH_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.OBJECT_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.PREDICATE_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.QUERY_LANGUAGE_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.QUERY_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.REMOVE_GRAPH_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.SUBJECT_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.USING_GRAPH_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.USING_NAMED_GRAPH_PARAM_NAME;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

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

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.AbstractController;

import info.aduna.lang.FileFormat;
import info.aduna.lang.service.FileFormatServiceRegistry;
import info.aduna.webapp.views.EmptySuccessView;
import info.aduna.webapp.views.SimpleResponseView;

import org.openrdf.http.protocol.Protocol;
import org.openrdf.http.protocol.Protocol.Action;
import org.openrdf.http.protocol.error.ErrorInfo;
import org.openrdf.http.protocol.error.ErrorType;
import org.openrdf.http.server.ClientHTTPException;
import org.openrdf.http.server.HTTPException;
import org.openrdf.http.server.ProtocolUtil;
import org.openrdf.http.server.ServerHTTPException;
import org.openrdf.http.server.repository.BooleanQueryResultView;
import org.openrdf.http.server.repository.GraphQueryResultView;
import org.openrdf.http.server.repository.QueryResultView;
import org.openrdf.http.server.repository.RepositoryInterceptor;
import org.openrdf.http.server.repository.TupleQueryResultView;
import org.openrdf.http.server.repository.statements.ExportStatementsView;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.IRI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.SESAME;
import org.openrdf.query.BooleanQuery;
import org.openrdf.query.GraphQuery;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.Query;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryInterruptedException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.UnsupportedQueryLanguageException;
import org.openrdf.query.Update;
import org.openrdf.query.UpdateExecutionException;
import org.openrdf.query.impl.SimpleDataset;
import org.openrdf.query.resultio.BooleanQueryResultWriterRegistry;
import org.openrdf.query.resultio.TupleQueryResultWriterRegistry;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.RDFWriterFactory;
import org.openrdf.rio.RDFWriterRegistry;
import org.openrdf.rio.Rio;
import org.openrdf.rio.helpers.BasicParserSettings;
import org.openrdf.rio.helpers.AbstractRDFHandler;

/**
 * Handles requests for transaction creation on a repository.
 * 
 * @since 2.8.0
 * @author Jeen Broekstra
 */
public class TransactionController extends AbstractController {

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

    public TransactionController() throws ApplicationContextException {
        setSupportedMethods(new String[] { 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());
        RepositoryConnection connection = ActiveTransactionRegistry.INSTANCE
                .getTransactionConnection(transactionId);

        if (connection == null) {
            logger.warn("could not find connection for transaction id {}", transactionId);
            throw new ClientHTTPException(SC_BAD_REQUEST,
                    "unable to find registerd connection 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(connection, transactionId, 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(connection, transactionId, 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(connection, transactionId, request, response);
                logger.info("{} txn size request finished", reqMethod);
            } else {
                throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                        "Method not allowed: " + reqMethod);
            }
            break;
        default:
            // modification operations - we can process these and then
            // immediately release the connection back to the registry.
            try {
                // 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 {
                        connection.rollback();
                    } finally {
                        ActiveTransactionRegistry.INSTANCE.deregister(transactionId);
                        connection.close();
                    }
                    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(connection, action, request, response);
                    logger.info("PUT txn operation request finished.");
                } else {
                    throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                            "Method not allowed: " + reqMethod);
                }
            } finally {
                ActiveTransactionRegistry.INSTANCE.returnTransactionConnection(transactionId);
            }
            break;
        }
        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(RepositoryConnection conn, Action action,
            HttpServletRequest request, HttpServletResponse response) throws IOException, HTTPException {
        ProtocolUtil.logRequestParameters(request);

        Map<String, Object> model = new HashMap<String, Object>();

        String baseURI = request.getParameter(Protocol.BASEURI_PARAM_NAME);
        if (baseURI == null) {
            baseURI = "";
        }

        try {
            switch (action) {
            case ADD:
                conn.add(request.getInputStream(), baseURI, Rio.getParserFormatForMIMEType(request.getContentType())
                        .orElseThrow(Rio.unsupportedFormat(request.getContentType())));
                break;
            case DELETE:
                RDFParser parser = Rio.createParser(Rio.getParserFormatForMIMEType(request.getContentType())
                        .orElseThrow(Rio.unsupportedFormat(request.getContentType())), conn.getValueFactory());
                parser.setRDFHandler(new WildcardRDFRemover(conn));
                parser.getParserConfig().set(BasicParserSettings.PRESERVE_BNODE_IDS, true);
                parser.parse(request.getInputStream(), baseURI);
                break;
            case UPDATE:
                return getSparqlUpdateResult(conn, request, response);
            case COMMIT:
                conn.commit();
                conn.close();
                ActiveTransactionRegistry.INSTANCE.deregister(getTransactionID(request));
                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(RepositoryConnection conn, UUID txnId, HttpServletRequest request,
            HttpServletResponse response) throws HTTPException {
        try {
            ProtocolUtil.logRequestParameters(request);

            Map<String, Object> model = new HashMap<String, Object>();
            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 = -1;

                try {
                    size = conn.size(contexts);
                } catch (RepositoryException e) {
                    throw new ServerHTTPException("Repository error: " + e.getMessage(), e);
                }
                model.put(SimpleResponseView.CONTENT_KEY, String.valueOf(size));
            }

            return new ModelAndView(SimpleResponseView.getInstance(), model);
        } finally {
            ActiveTransactionRegistry.INSTANCE.returnTransactionConnection(txnId);
        }
    }

    /**
     * Get all statements and export them as RDF.
     * 
     * @return a model and view for exporting the statements.
     */
    private ModelAndView getExportStatementsResult(RepositoryConnection conn, UUID txnId,
            HttpServletRequest request, HttpServletResponse response) throws ClientHTTPException {
        ProtocolUtil.logRequestParameters(request);

        ValueFactory vf = conn.getValueFactory();

        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<String, Object>();
        model.put(ExportStatementsView.SUBJECT_KEY, subj);
        model.put(ExportStatementsView.PREDICATE_KEY, pred);
        model.put(ExportStatementsView.OBJECT_KEY, obj);
        model.put(ExportStatementsView.CONTEXTS_KEY, contexts);
        model.put(ExportStatementsView.USE_INFERENCING_KEY, Boolean.valueOf(useInferencing));
        model.put(ExportStatementsView.FACTORY_KEY, rdfWriterFactory);
        model.put(ExportStatementsView.HEADERS_ONLY, METHOD_HEAD.equals(request.getMethod()));
        model.put(ExportStatementsView.CONNECTION_KEY, conn);
        model.put(ExportStatementsView.TRANSACTION_ID_KEY, txnId);
        return new ModelAndView(ExportStatementsView.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(RepositoryConnection conn, UUID txnId, HttpServletRequest request,
            HttpServletResponse response) throws IOException, HTTPException {
        String queryStr = null;
        final String contentType = request.getContentType();
        if (contentType != null && contentType.contains(Protocol.SPARQL_QUERY_MIME_TYPE)) {
            final String encoding = request.getCharacterEncoding() != null ? request.getCharacterEncoding()
                    : "UTF-8";
            queryStr = IOUtils.toString(request.getInputStream(), encoding);
        } else {
            queryStr = request.getParameter(QUERY_PARAM_NAME);
        }

        Query query = getQuery(conn, queryStr, request, response);

        View view;
        Object queryResult;
        FileFormatServiceRegistry<? extends FileFormat, ?> registry;

        try {
            if (query instanceof TupleQuery) {
                TupleQuery tQuery = (TupleQuery) query;

                queryResult = tQuery.evaluate();
                registry = TupleQueryResultWriterRegistry.getInstance();
                view = TupleQueryResultView.getInstance();
            } else if (query instanceof GraphQuery) {
                GraphQuery gQuery = (GraphQuery) query;

                queryResult = gQuery.evaluate();
                registry = RDFWriterRegistry.getInstance();
                view = GraphQueryResultView.getInstance();
            } else if (query instanceof BooleanQuery) {
                BooleanQuery bQuery = (BooleanQuery) query;

                queryResult = bQuery.evaluate();
                registry = BooleanQueryResultWriterRegistry.getInstance();
                view = BooleanQueryResultView.getInstance();
            } else {
                throw new ClientHTTPException(SC_BAD_REQUEST,
                        "Unsupported query type: " + query.getClass().getName());
            }
        } catch (QueryInterruptedException e) {
            logger.info("Query interrupted", e);
            ActiveTransactionRegistry.INSTANCE.returnTransactionConnection(txnId);
            throw new ServerHTTPException(SC_SERVICE_UNAVAILABLE, "Query evaluation took too long");
        } catch (QueryEvaluationException e) {
            logger.info("Query evaluation error", e);
            ActiveTransactionRegistry.INSTANCE.returnTransactionConnection(txnId);
            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<String, Object>();
        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.
        model.put(QueryResultView.TRANSACTION_ID_KEY, txnId);
        return new ModelAndView(view, model);
    }

    private Query getQuery(RepositoryConnection repositoryCon, String queryStr, HttpServletRequest request,
            HttpServletResponse response) throws IOException, ClientHTTPException {
        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 = repositoryCon.getValueFactory().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 = repositoryCon.getValueFactory().createIRI(namedGraphURI);
                        }
                        dataset.addNamedGraph(uri);
                    } catch (IllegalArgumentException e) {
                        throw new ClientHTTPException(SC_BAD_REQUEST,
                                "Illegal URI for named graph: " + namedGraphURI);
                    }
                }
            }
        }

        try {
            result = repositoryCon.prepareQuery(queryLn, queryStr, baseURI);

            result.setIncludeInferred(includeInferred);

            if (maxQueryTime > 0) {
                result.setMaxQueryTime(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,
                            repositoryCon.getValueFactory());
                    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(RepositoryConnection conn, HttpServletRequest request,
            HttpServletResponse response) throws ServerHTTPException, ClientHTTPException, HTTPException {
        String sparqlUpdateString = null;
        final String contentType = request.getContentType();
        if (contentType != null && contentType.contains(Protocol.SPARQL_UPDATE_MIME_TYPE)) {
            try {
                final String encoding = request.getCharacterEncoding() != null ? request.getCharacterEncoding()
                        : "UTF-8";
                sparqlUpdateString = IOUtils.toString(request.getInputStream(), encoding);
            } 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);
        }

        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 = conn.getValueFactory().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 = conn.getValueFactory().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 = conn.getValueFactory().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 = conn.getValueFactory().createIRI(namedGraphURI);
                    }
                    dataset.addNamedGraph(uri);
                } catch (IllegalArgumentException e) {
                    throw new ClientHTTPException(SC_BAD_REQUEST, "Illegal URI for named graph: " + namedGraphURI);
                }
            }
        }

        try {
            Update update = conn.prepareUpdate(queryLn, sparqlUpdateString, baseURI);

            update.setIncludeInferred(includeInferred);

            if (dataset != null) {
                update.setDataset(dataset);
            }

            // determine if any variable bindings have been set on this update.
            @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,
                            conn.getValueFactory());
                    update.setBinding(bindingName, bindingValue);
                }
            }

            update.execute();

            return new ModelAndView(EmptySuccessView.getInstance());
        } catch (UpdateExecutionException 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);
            }
        } catch (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);
            }
        } catch (MalformedQueryException e) {
            ErrorInfo errInfo = new ErrorInfo(ErrorType.MALFORMED_QUERY, e.getMessage());
            throw new ClientHTTPException(SC_BAD_REQUEST, errInfo.toString());
        }
    }

    private static class WildcardRDFRemover extends AbstractRDFHandler {

        private final RepositoryConnection conn;

        public WildcardRDFRemover(RepositoryConnection conn) {
            super();
            this.conn = conn;
        }

        @Override
        public void handleStatement(Statement st) throws RDFHandlerException {
            Resource subject = SESAME.WILDCARD.equals(st.getSubject()) ? null : st.getSubject();
            IRI predicate = SESAME.WILDCARD.equals(st.getPredicate()) ? null : st.getPredicate();
            Value object = SESAME.WILDCARD.equals(st.getObject()) ? null : st.getObject();
            Resource context = st.getContext();
            try {
                if (context != null) {
                    conn.remove(subject, predicate, object, st.getContext());
                } else {
                    conn.remove(subject, predicate, object);
                }
            } catch (RepositoryException e) {
                throw new RDFHandlerException(e);
            }
        }

    }
}