All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jena.fuseki.servlets.SPARQL_Query Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.apache.jena.fuseki.servlets;

import static java.lang.String.format ;
import static org.apache.jena.fuseki.HttpNames.* ;
import static org.apache.jena.fuseki.server.CounterName.QueryExecErrors ;
import static org.apache.jena.fuseki.server.CounterName.QueryTimeouts ;
import static org.apache.jena.fuseki.server.CounterName.RequestsBad ;

import java.io.IOException ;
import java.io.InputStream ;
import java.util.* ;

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

import org.apache.jena.atlas.RuntimeIOException ;
import org.apache.jena.atlas.io.IO ;
import org.apache.jena.atlas.io.IndentedLineBuffer ;
import org.apache.jena.atlas.web.ContentType ;
import org.apache.jena.fuseki.FusekiException ;
import org.apache.jena.fuseki.FusekiLib ;
import org.apache.jena.fuseki.HttpNames ;
import org.apache.jena.query.* ;
import org.apache.jena.rdf.model.Model ;
import org.apache.jena.riot.WebContent ;
import org.apache.jena.riot.web.HttpOp ;
import org.apache.jena.sparql.core.Prologue ;
import org.apache.jena.sparql.resultset.SPARQLResult ;
import org.apache.jena.web.HttpSC ;

/**
 * Handles SPARQL Query requests.
 */
public abstract class SPARQL_Query extends SPARQL_Protocol
{
    public SPARQL_Query()   { super() ; }

    // Choose REST verbs to support.
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    { doCommon(request, response) ; }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    { doCommon(request, response) ; }

    // HEAD
    
    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response)
    {
        setCommonHeadersForOptions(response) ;
        response.setHeader(HttpNames.hAllow, "GET,OPTIONS,POST");
        response.setHeader(HttpNames.hContentLengh, "0") ;
    }
    
    @Override
    protected final void perform(HttpAction action)
    {
        // GET
        if ( action.request.getMethod().equals(HttpNames.METHOD_GET) ) {
            executeWithParameter(action) ;
            return ;
        }

        ContentType ct = FusekiLib.getContentType(action) ;
        if ( ct == null ) {
            // Validation check it's POST with ?query=
            executeWithParameter(action) ;
            return ;
        }
        
        String incoming = ct.getContentType() ;
        // POST application/sparql-query
        if (WebContent.contentTypeSPARQLQuery.equals(incoming)) {
            executeBody(action) ;
            return ;
        }
        // POST application/x-www-form-url
        if (WebContent.contentTypeHTMLForm.equals(incoming)) {
            executeWithParameter(action) ;
            return ;
        }
        
        error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Bad content type: "+incoming) ;
    }

    // All the params we support

    protected static List allParams  = Arrays.asList(paramQuery, 
                                                             paramDefaultGraphURI, paramNamedGraphURI, 
                                                             paramQueryRef,
                                                             paramStyleSheet,
                                                             paramAccept,
                                                             paramOutput1, paramOutput2, 
                                                             paramCallback, 
                                                             paramForceAccept,
                                                             paramTimeout) ;

    @Override
    protected void validate(HttpAction action)
    {
        String method = action.request.getMethod().toUpperCase(Locale.ROOT) ;
        
        if ( ! HttpNames.METHOD_POST.equals(method) && ! HttpNames.METHOD_GET.equals(method) )
            errorMethodNotAllowed("Not a GET or POST request") ;
            
        if ( HttpNames.METHOD_GET.equals(method) && action.request.getQueryString() == null )
        {
            warning("Service Description / SPARQL Query / "+action.request.getRequestURI()) ;
            errorNotFound("Service Description: "+action.request.getRequestURI()) ;
        }
        
        // Use of the dataset describing parameters is check later.
        try {
            validateParams(action.request, allParams) ;
            validateRequest(action) ; 
        } catch (ActionErrorException ex) { 
            throw ex ; 
        } 
        // Query not yet parsed.
    }
    
    /**
     * Validate the request after checking HTTP method and HTTP Parameters.
     * @param action HTTP Action
     */
    protected abstract void validateRequest(HttpAction action) ;
    
    /**
     * Helper method for validating request.
     * @param request HTTP request
     * @param params parameters in a collection of Strings
     */
    protected void validateParams(HttpServletRequest request, Collection params)
    {
        ContentType ct = FusekiLib.getContentType(request) ;
        boolean mustHaveQueryParam = true ;
        if ( ct != null )
        {
            String incoming = ct.getContentType() ;
            
            if ( WebContent.contentTypeSPARQLQuery.equals(incoming) )
            {
                mustHaveQueryParam = false ;
                //error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Unofficial "+WebContent.contentTypeSPARQLQuery+" not supported") ;
            }
            else if ( WebContent.contentTypeHTMLForm.equals(incoming) ) {}
            else
                error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported: "+incoming) ;
        }
        
        // GET/POST of a form at this point.
        
        if ( mustHaveQueryParam )
        {
            int N = countParamOccurences(request, paramQuery) ; 
            
            if ( N == 0 ) errorBadRequest("SPARQL Query: No 'query=' parameter") ;
            if ( N > 1 ) errorBadRequest("SPARQL Query: Multiple 'query=' parameters") ;
            
            // application/sparql-query does not use a query param.
            String queryStr = request.getParameter(HttpNames.paramQuery) ;
            
            if ( queryStr == null )
                errorBadRequest("SPARQL Query: No query specified (no 'query=' found)") ;
            if ( queryStr.isEmpty() )
                errorBadRequest("SPARQL Query: Empty query string") ;
        }

        if ( params != null )
        {
            Enumeration en = request.getParameterNames() ;
            for ( ; en.hasMoreElements() ; )
            {
                String name = en.nextElement() ;
                if ( ! params.contains(name) )
                    warning("SPARQL Query: Unrecognize request parameter (ignored): "+name) ;
            }
        }
    }

    private void executeWithParameter(HttpAction action)
    {
        String queryString = action.request.getParameter(paramQuery) ;
        execute(queryString, action) ;
    }

    private void executeBody(HttpAction action)
    {
        String queryString = null ;
        try { 
            InputStream input = action.request.getInputStream() ; 
            queryString = IO.readWholeFileAsUTF8(input) ;
        }
        catch (IOException ex) { errorOccurred(ex) ; }
        execute(queryString, action) ;
    }

    private void execute(String queryString, HttpAction action)
    {
        String queryStringLog = formatForLog(queryString) ;
        if ( action.verbose )
            log.info(format("[%d] Query = \n%s", action.id, queryString));
        else
            log.info(format("[%d] Query = %s", action.id, queryStringLog));

        Query query = null ;
        try {
            // NB syntax is ARQ (a superset of SPARQL)
            query = QueryFactory.create(queryString, "http://example/query-base", Syntax.syntaxARQ) ;
            queryStringLog = formatForLog(query) ;
            validateQuery(action, query) ;
        } catch (ActionErrorException ex) {
            incCounter(action.srvRef, RequestsBad) ;
            throw ex ;
        } catch (QueryParseException ex) {
            incCounter(action.srvRef, RequestsBad) ;
            errorBadRequest("Parse error: \n" + queryString + "\n\r" + messageForQPE(ex)) ;
        } catch (RuntimeIOException ex) {
            errorBadRequest("Runtime IO Exception: \n" + queryString + "\n\r" + ex.getMessage()) ;
        }
        // Should not happen.
        catch (QueryException ex) {
            errorBadRequest("Error: \n" + queryString + "\n\r" + ex.getMessage()) ;
        }
        
        // Assumes finished whole thing by end of sendResult. 
        action.beginRead() ;
        try {
            Dataset dataset = decideDataset(action, query, queryStringLog) ; 
            try(QueryExecution qExec = createQueryExecution(query, dataset)) {
                SPARQLResult result = executeQuery(action, qExec, query, queryStringLog) ;
                // Deals with exceptions itself.
                sendResults(action, result, query.getPrologue()) ;
            }
        } catch (QueryCancelledException ex) {
            // Additional counter information.
            incCounter(action.srvRef, QueryTimeouts) ; 
            throw ex ; 
        } catch (RuntimeIOException ex) {
            incCounter(action.srvRef, QueryExecErrors) ;
            throw ex ;
        } catch (QueryExecException ex) { 
            // Additional counter information.
            incCounter(action.srvRef, QueryExecErrors) ; 
            throw ex ;
        } finally { 
            action.endRead() ;
        }
    }

    /**
     * Check the query, throwing ActionErrorException when not valid, or calling super#error.
     * @param action HTTP Action
     * @param query the Query
     */
    protected abstract void validateQuery(HttpAction action, Query query) ;

    protected QueryExecution createQueryExecution(Query query, Dataset dataset)
    {
        return QueryExecutionFactory.create(query, dataset) ;
    }

    protected SPARQLResult executeQuery(HttpAction action, QueryExecution qExec, Query query, String queryStringLog)
    {
        setAnyTimeouts(qExec, action);

        if ( query.isSelectType() )
        {
            ResultSet rs = qExec.execSelect() ;
            
            // Force some query execution now.
            //
            // If the timeout-first-row goes off, the output stream has not 
            // been started so the HTTP error code is sent. 
            
            rs.hasNext() ;

            // If we wanted perfect query time cancellation, we could consume the result now
            // to see if the timeout-end-of-query goes off.  
            
            //rs = ResultSetFactory.copyResults(rs) ;

            log.info(format("[%d] exec/select", action.id)) ;
            return new SPARQLResult(rs) ;
        }

        if ( query.isConstructType() )
        {
            Model model = qExec.execConstruct() ;
            log.info(format("[%d] exec/construct", action.id)) ;
            return new SPARQLResult(model) ;
        }

        if ( query.isDescribeType() )
        {
            Model model = qExec.execDescribe() ;
            log.info(format("[%d] exec/describe",action.id)) ;
            return new SPARQLResult(model) ;
        }

        if ( query.isAskType() )
        {
            boolean b = qExec.execAsk() ;
            log.info(format("[%d] exec/ask",action.id)) ;
            return new SPARQLResult(b) ;
        }

        errorBadRequest("Unknown query type - "+queryStringLog) ;
        return null ;
    }

    private void setAnyTimeouts(QueryExecution qexec, HttpAction action) {
        if (!(action.getDatasetRef().allowTimeoutOverride))
            return;

        long desiredTimeout = Long.MAX_VALUE;
        String timeoutHeader = action.request.getHeader("Timeout");
        String timeoutParameter = action.request.getParameter("timeout");
        if (timeoutHeader != null) {
            try {
                desiredTimeout = (int) Float.parseFloat(timeoutHeader) * 1000;
            } catch (NumberFormatException e) {
                throw new FusekiException("Timeout header must be a number", e);
            }
        } else if (timeoutParameter != null) {
            try {
                desiredTimeout = (int) Float.parseFloat(timeoutParameter) * 1000;
            } catch (NumberFormatException e) {
                throw new FusekiException("timeout parameter must be a number", e);
            }
        }

        desiredTimeout = Math.min(action.getDatasetRef().maximumTimeoutOverride, desiredTimeout);
        if (desiredTimeout != Long.MAX_VALUE)
            qexec.setTimeout(desiredTimeout);
    }

    protected abstract Dataset decideDataset(HttpAction action, Query query, String queryStringLog) ;

    protected void sendResults(HttpAction action, SPARQLResult result, Prologue qPrologue)
    {
        if ( result.isResultSet() )
            ResponseResultSet.doResponseResultSet(action, result.getResultSet(), qPrologue) ;
        else if ( result.isGraph() )
            ResponseModel.doResponseModel(action, result.getModel()) ;
        else if ( result.isBoolean() )
            ResponseResultSet.doResponseResultSet(action, result.getBooleanResult()) ;
        else
            errorOccurred("Unknown or invalid result type") ;
    }
    
    private String formatForLog(Query query)
    {
        IndentedLineBuffer out = new IndentedLineBuffer() ;
        out.setFlatMode(true) ;
        query.serialize(out) ;
        return out.asString() ;
    }
        
    private String getRemoteString(String queryURI)
    {
        return HttpOp.execHttpGetString(queryURI) ;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy