org.openrdf.http.server.repository.statements.StatementsController.java Source code

Java tutorial

Introduction

Here is the source code for org.openrdf.http.server.repository.statements.StatementsController.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.statements;

import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
import static org.openrdf.http.protocol.Protocol.BASEURI_PARAM_NAME;
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.INCLUDE_INFERRED_PARAM_NAME;
import static org.openrdf.http.protocol.Protocol.INSERT_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.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.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

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.mvc.AbstractController;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import info.aduna.webapp.util.HttpServerUtil;
import info.aduna.webapp.views.EmptySuccessView;

import org.openrdf.http.protocol.Protocol;
import org.openrdf.http.protocol.error.ErrorInfo;
import org.openrdf.http.protocol.error.ErrorType;
import org.openrdf.http.protocol.transaction.TransactionReader;
import org.openrdf.http.protocol.transaction.operations.TransactionOperation;
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.RepositoryInterceptor;
import org.openrdf.model.Resource;
import org.openrdf.model.IRI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.Update;
import org.openrdf.query.UpdateExecutionException;
import org.openrdf.query.impl.SimpleDataset;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.RDFWriterFactory;
import org.openrdf.rio.RDFWriterRegistry;
import org.openrdf.rio.Rio;
import org.openrdf.rio.UnsupportedRDFormatException;
import org.openrdf.rio.helpers.BasicParserSettings;

/**
 * Handles requests for manipulating the statements in a repository.
 * 
 * @author Herko ter Horst
 * @author Arjohn Kampman
 */
public class StatementsController extends AbstractController {

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

    public StatementsController() throws ApplicationContextException {
        setSupportedMethods(new String[] { METHOD_GET, METHOD_POST, METHOD_HEAD, "PUT", "DELETE" });
    }

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        ModelAndView result;

        Repository repository = RepositoryInterceptor.getRepository(request);

        String reqMethod = request.getMethod();

        if (METHOD_GET.equals(reqMethod)) {
            logger.info("GET statements");
            result = getExportStatementsResult(repository, request, response);
        } else if (METHOD_HEAD.equals(reqMethod)) {
            logger.info("HEAD statements");
            result = getExportStatementsResult(repository, request, response);
        } else if (METHOD_POST.equals(reqMethod)) {
            String mimeType = HttpServerUtil.getMIMEType(request.getContentType());

            if (Protocol.TXN_MIME_TYPE.equals(mimeType)) {
                logger.info("POST transaction to repository");
                result = getTransactionResultResult(repository, request, response);
            } else if (Protocol.SPARQL_UPDATE_MIME_TYPE.equals(mimeType)
                    || request.getParameterMap().containsKey(Protocol.UPDATE_PARAM_NAME)) {
                logger.info("POST SPARQL update request to repository");
                result = getSparqlUpdateResult(repository, request, response);
            } else {
                logger.info("POST data to repository");
                result = getAddDataResult(repository, request, response, false);
            }
        } else if ("PUT".equals(reqMethod)) {
            logger.info("PUT data in repository");
            result = getAddDataResult(repository, request, response, true);
        } else if ("DELETE".equals(reqMethod)) {
            logger.info("DELETE data from repository");
            result = getDeleteDataResult(repository, request, response);
        } else {
            throw new ClientHTTPException(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                    "Method not allowed: " + reqMethod);
        }

        return result;
    }

    private ModelAndView getSparqlUpdateResult(Repository repository, HttpServletRequest request,
            HttpServletResponse response) throws ServerHTTPException, ClientHTTPException, HTTPException {
        ProtocolUtil.logRequestParameters(request);

        String mimeType = HttpServerUtil.getMIMEType(request.getContentType());

        String sparqlUpdateString;
        if (Protocol.SPARQL_UPDATE_MIME_TYPE.equals(mimeType)) {
            // The query should be the entire body
            try {
                sparqlUpdateString = IOUtils.toString(request.getReader());
            } catch (IOException e) {
                throw new ClientHTTPException(SC_BAD_REQUEST, "Error reading request message body", e);
            }
            if (sparqlUpdateString.isEmpty())
                sparqlUpdateString = null;
        } else {
            sparqlUpdateString = request.getParameterValues(Protocol.UPDATE_PARAM_NAME)[0];
        }

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

        try {

            RepositoryConnection repositoryCon = RepositoryInterceptor.getRepositoryConnection(request);
            synchronized (repositoryCon) {
                Update update = repositoryCon.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,
                                repository.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 IRI createURIOrNull(Repository repository, String graphURI) {
        if ("null".equals(graphURI))
            return null;
        return repository.getValueFactory().createIRI(graphURI);
    }

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

        ValueFactory vf = repository.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()));
        return new ModelAndView(ExportStatementsView.getInstance(), model);
    }

    /**
     * Process several actions as a transaction.
     */
    private ModelAndView getTransactionResultResult(Repository repository, HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ClientHTTPException, ServerHTTPException, HTTPException {
        InputStream in = request.getInputStream();
        try {
            logger.debug("Processing transaction...");

            TransactionReader reader = new TransactionReader();
            Iterable<? extends TransactionOperation> txn = reader.parse(in);

            RepositoryConnection repositoryCon = RepositoryInterceptor.getRepositoryConnection(request);
            synchronized (repositoryCon) {
                repositoryCon.begin();

                for (TransactionOperation op : txn) {
                    op.execute(repositoryCon);
                }

                repositoryCon.commit();
            }
            logger.debug("Transaction processed ");

            return new ModelAndView(EmptySuccessView.getInstance());
        } catch (SAXParseException e) {
            ErrorInfo errInfo = new ErrorInfo(ErrorType.MALFORMED_DATA, e.getMessage());
            throw new ClientHTTPException(SC_BAD_REQUEST, errInfo.toString());
        } catch (SAXException e) {
            throw new ServerHTTPException("Failed to parse transaction data: " + e.getMessage(), e);
        } catch (IOException e) {
            throw new ServerHTTPException("Failed to read data: " + 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);
            }
        }
    }

    /**
     * Upload data to the repository.
     */
    private ModelAndView getAddDataResult(Repository repository, HttpServletRequest request,
            HttpServletResponse response, boolean replaceCurrent)
            throws IOException, ServerHTTPException, ClientHTTPException, HTTPException {
        ProtocolUtil.logRequestParameters(request);

        String mimeType = HttpServerUtil.getMIMEType(request.getContentType());

        RDFFormat rdfFormat = Rio.getParserFormatForMIMEType(mimeType).orElseThrow(
                () -> new ClientHTTPException(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported MIME type: " + mimeType));

        ValueFactory vf = repository.getValueFactory();

        Resource[] contexts = ProtocolUtil.parseContextParam(request, CONTEXT_PARAM_NAME, vf);
        IRI baseURI = ProtocolUtil.parseURIParam(request, BASEURI_PARAM_NAME, vf);
        final boolean preserveNodeIds = ProtocolUtil.parseBooleanParam(request,
                Protocol.PRESERVE_BNODE_ID_PARAM_NAME, false);

        if (baseURI == null) {
            baseURI = vf.createIRI("foo:bar");
            logger.info("no base URI specified, using dummy '{}'", baseURI);
        }

        InputStream in = request.getInputStream();
        try {
            RepositoryConnection repositoryCon = RepositoryInterceptor.getRepositoryConnection(request);
            synchronized (repositoryCon) {
                if (!repositoryCon.isActive()) {
                    repositoryCon.begin();
                }

                if (preserveNodeIds) {
                    repositoryCon.getParserConfig().set(BasicParserSettings.PRESERVE_BNODE_IDS, true);
                }

                if (replaceCurrent) {
                    repositoryCon.clear(contexts);
                }
                repositoryCon.add(in, baseURI.toString(), rdfFormat, contexts);

                repositoryCon.commit();
            }

            return new ModelAndView(EmptySuccessView.getInstance());
        } catch (UnsupportedRDFormatException e) {
            throw new ClientHTTPException(SC_UNSUPPORTED_MEDIA_TYPE,
                    "No RDF parser available for format " + rdfFormat.getName());
        } catch (RDFParseException e) {
            ErrorInfo errInfo = new ErrorInfo(ErrorType.MALFORMED_DATA, e.getMessage());
            throw new ClientHTTPException(SC_BAD_REQUEST, errInfo.toString());
        } catch (IOException e) {
            throw new ServerHTTPException("Failed to read data: " + 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);
            }
        }
    }

    /**
     * Delete data from the repository.
     */
    private ModelAndView getDeleteDataResult(Repository repository, HttpServletRequest request,
            HttpServletResponse response) throws ServerHTTPException, ClientHTTPException, HTTPException {
        ProtocolUtil.logRequestParameters(request);

        ValueFactory vf = repository.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);

        try {
            RepositoryConnection repositoryCon = RepositoryInterceptor.getRepositoryConnection(request);
            synchronized (repositoryCon) {
                repositoryCon.remove(subj, pred, obj, contexts);
            }

            return new ModelAndView(EmptySuccessView.getInstance());
        } 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);
            }
        }
    }
}