Add construct quad support to Fuseki Project: http://git-wip-us.apache.org/repos/asf/jena/repo Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/0eb28d80 Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/0eb28d80 Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/0eb28d80
Branch: refs/heads/master Commit: 0eb28d805e3270c9951f13cdbd55af3e8d01d0ab Parents: e819ac3 Author: confidencesun <[email protected]> Authored: Tue Aug 11 12:05:55 2015 +0800 Committer: confidencesun <[email protected]> Committed: Tue Aug 11 12:05:55 2015 +0800 ---------------------------------------------------------------------- .../sparql/engine/http/QueryEngineHTTP.java | 30 +- .../main/java/org/apache/jena/fuseki/DEF.java | 7 + .../jena/fuseki/servlets/ResponseDataset.java | 134 +++ .../jena/fuseki/servlets/SPARQL_Query.java | 838 ++++++++++--------- .../java/org/apache/jena/fuseki/TestQuery.java | 188 ++--- 5 files changed, 719 insertions(+), 478 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jena/blob/0eb28d80/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryEngineHTTP.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryEngineHTTP.java b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryEngineHTTP.java index a35fffc..cab831f 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryEngineHTTP.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryEngineHTTP.java @@ -394,7 +394,7 @@ public class QueryEngineHTTP implements QueryExecution { @Override public Iterator<Quad> execConstructQuads(){ - return null; + return execQuads(); } @Override @@ -469,6 +469,32 @@ public class QueryEngineHTTP implements QueryExecution { return RiotReader.createIteratorTriples(in, lang, null); } + + private Iterator<Quad> execQuads() { + checkNotClosed() ; + HttpQuery httpQuery = makeHttpQuery(); + httpQuery.setAccept(WebContent.defaultDatasetAcceptHeader); + InputStream in = httpQuery.exec(); + + // Don't assume the endpoint actually gives back the content type we + // asked for + String actualContentType = httpQuery.getContentType(); + + // If the server fails to return a Content-Type then we will assume + // the server returned the type we asked for + if (actualContentType == null || actualContentType.equals("")) { + actualContentType = WebContent.defaultDatasetAcceptHeader; + } + + // Try to select language appropriately here based on the model content + // type + Lang lang = RDFLanguages.contentTypeToLang(actualContentType); + if (!RDFLanguages.isQuads(lang)) + throw new QueryException("Endpoint returned Content Type: " + actualContentType + + " which is not a valid RDF Dataset syntax"); + + return RiotReader.createIteratorQuads(in, lang, null); + } @Override public boolean execAsk() { @@ -817,4 +843,4 @@ public class QueryEngineHTTP implements QueryExecution { if ( v < 1 ) sBuff.append(";q=").append(v) ; } -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/0eb28d80/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java index b419cc6..953b724 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/DEF.java @@ -31,6 +31,13 @@ public class DEF public static final AcceptList jsonOffer = AcceptList.create(contentTypeJSON) ; + public static final AcceptList pureRdfOffer = AcceptList.create(contentTypeTurtle, + contentTypeTurtleAlt1, + contentTypeTurtleAlt2, + contentTypeNTriples, + contentTypeRDFXML + ) ; + public static final AcceptList rdfOffer = AcceptList.create(contentTypeTurtle, contentTypeTurtleAlt1, contentTypeTurtleAlt2, http://git-wip-us.apache.org/repos/asf/jena/blob/0eb28d80/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java new file mode 100644 index 0000000..91d615d --- /dev/null +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java @@ -0,0 +1,134 @@ +/* + * 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 org.apache.jena.riot.WebContent.charsetUTF8; +import static org.apache.jena.riot.WebContent.contentTypeJSONLD; +import static org.apache.jena.riot.WebContent.contentTypeNTriples; +import static org.apache.jena.riot.WebContent.contentTypeRDFJSON; +import static org.apache.jena.riot.WebContent.contentTypeRDFXML; +import static org.apache.jena.riot.WebContent.contentTypeTurtle; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.jena.atlas.web.MediaType; +import org.apache.jena.fuseki.DEF; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.conneg.ConNeg; +import org.apache.jena.fuseki.conneg.WebLib; +import org.apache.jena.query.Dataset; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.riot.RDFLanguages; +import org.apache.jena.riot.WebContent; +import org.apache.jena.web.HttpSC; + +public class ResponseDataset +{ + // Short names for "output=" + private static final String contentOutputTriG = "trig" ; + private static final String contentOutputNQuads = "n-quads" ; + + + public static Map<String,String> shortNamesModel = new HashMap<String, String>() ; + static { + + // Some short names. keys are lowercase. + + ResponseOps.put(shortNamesModel, contentOutputNQuads, WebContent.contentTypeNQuads) ; + ResponseOps.put(shortNamesModel, contentOutputTriG, WebContent.contentTypeTriG) ; + } + + public static void doResponseDataset(HttpAction action, Dataset dataset) + { + HttpServletRequest request = action.request ; + HttpServletResponse response = action.response ; + + String mimeType = null ; // Header request type + + // TODO Use MediaType throughout. + MediaType i = ConNeg.chooseContentType(request, DEF.quadsOffer, DEF.acceptNQuads) ; + if ( i != null ) + mimeType = i.getContentType() ; + + String outputField = ResponseOps.paramOutput(request, shortNamesModel) ; + if ( outputField != null ) + mimeType = outputField ; + + String writerMimeType = mimeType ; + + if ( mimeType == null ) + { + Fuseki.actionLog.warn("Can't find MIME type for response") ; + String x = WebLib.getAccept(request) ; + String msg ; + if ( x == null ) + msg = "No Accept: header" ; + else + msg = "Accept: "+x+" : Not understood" ; + ServletOps.error(HttpSC.NOT_ACCEPTABLE_406, msg) ; + } + + String contentType = mimeType ; + String charset = charsetUTF8 ; + + String forceAccept = ResponseOps.paramForceAccept(request) ; + if ( forceAccept != null ) + { + contentType = forceAccept ; + charset = charsetUTF8 ; + } + + Lang lang = RDFLanguages.contentTypeToLang(contentType) ; + if ( lang == null ) + ServletOps.errorBadRequest("Can't determine output content type: "+contentType) ; + +// if ( rdfw instanceof RDFXMLWriterI ) +// rdfw.setProperty("showXmlDeclaration", "true") ; + + // // Write locally to check it's possible. + // // Time/space tradeoff. + // try { + // OutputStream out = new NullOutputStream() ; + // RDFDataMgr.write(out, model, lang) ; + // IO.flush(out) ; + // } catch (JenaException ex) + // { + // SPARQL_ServletBase.errorOccurred(ex) ; + // } + + try { + ResponseResultSet.setHttpResponse(action, contentType, charset) ; + response.setStatus(HttpSC.OK_200) ; + ServletOutputStream out = response.getOutputStream() ; + RDFDataMgr.write(out, dataset, lang) ; + out.flush() ; + } + catch (Exception ex) { + action.log.info("Exception while writing the response model: "+ex.getMessage(), ex) ; + ServletOps.errorOccurred("Exception while writing the response model: "+ex.getMessage(), ex) ; + } + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/0eb28d80/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java index ba4a61a..bed63da 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java @@ -16,385 +16,463 @@ * limitations under the License. */ -package org.apache.jena.fuseki.servlets ; - -import static java.lang.String.format ; -import static org.apache.jena.fuseki.server.CounterName.QueryTimeouts ; -import static org.apache.jena.riot.WebContent.ctHTMLForm ; -import static org.apache.jena.riot.WebContent.ctSPARQLQuery ; -import static org.apache.jena.riot.WebContent.isHtmlForm ; -import static org.apache.jena.riot.WebContent.matchContentType ; -import static org.apache.jena.riot.web.HttpNames.paramAccept ; -import static org.apache.jena.riot.web.HttpNames.paramCallback ; -import static org.apache.jena.riot.web.HttpNames.paramDefaultGraphURI ; -import static org.apache.jena.riot.web.HttpNames.paramForceAccept ; -import static org.apache.jena.riot.web.HttpNames.paramNamedGraphURI ; -import static org.apache.jena.riot.web.HttpNames.paramOutput1 ; -import static org.apache.jena.riot.web.HttpNames.paramOutput2 ; -import static org.apache.jena.riot.web.HttpNames.paramQuery ; -import static org.apache.jena.riot.web.HttpNames.paramQueryRef ; -import static org.apache.jena.riot.web.HttpNames.paramStyleSheet ; -import static org.apache.jena.riot.web.HttpNames.paramTimeout ; - -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.io.IO ; -import org.apache.jena.atlas.io.IndentedLineBuffer ; -import org.apache.jena.atlas.web.ContentType ; -import org.apache.jena.fuseki.Fuseki ; -import org.apache.jena.fuseki.FusekiException ; -import org.apache.jena.fuseki.FusekiLib ; -import org.apache.jena.query.* ; -import org.apache.jena.rdf.model.Model ; -import org.apache.jena.riot.web.HttpNames ; -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 ; - -/** Handle SPARQL Query requests overt eh SPARQL Protocol. - * Subclasses provide this algorithm with the actual dataset to query, whether - * a dataset hosted by this server ({@link SPARQL_QueryDataset}) or - * speciifed in the protocol request ({@link SPARQL_QueryGeneral}). - */ -public abstract class SPARQL_Query extends SPARQL_Protocol -{ - private static final String QueryParseBase = Fuseki.BaseParserSPARQL ; - - 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) ; - - // POST application/x-www-form-url - // POST ?query= and no Content-Type - if ( ct == null || isHtmlForm(ct) ) { - // validation checked that if no Content-type, then its a POST with ?query= - executeWithParameter(action) ; - return ; - } - - // POST application/sparql-query - if ( matchContentType(ct, ctSPARQLQuery) ) { - executeBody(action) ; - return ; - } - - ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Bad content type: " + ct.getContentType()) ; - } - - // All the params we support - - protected static List<String> allParams = Arrays.asList(paramQuery, paramDefaultGraphURI, paramNamedGraphURI, - paramQueryRef, paramStyleSheet, paramAccept, paramOutput1, - paramOutput2, paramCallback, paramForceAccept, paramTimeout) ; - - /** - * Validate the request, checking HTTP method and HTTP Parameters. - * @param action HTTP Action - */ - @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) ) - ServletOps.errorMethodNotAllowed("Not a GET or POST request") ; - - if ( HttpNames.METHOD_GET.equals(method) && action.request.getQueryString() == null ) { - ServletOps.warning(action, "Service Description / SPARQL Query / " + action.request.getRequestURI()) ; - ServletOps.errorNotFound("Service Description: " + action.request.getRequestURI()) ; - } - - // Use of the dataset describing parameters is check later. - try { - validateParams(action, 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(HttpAction action, Collection<String> params) { - HttpServletRequest request = action.request ; - ContentType ct = FusekiLib.getContentType(request) ; - boolean mustHaveQueryParam = true ; - if ( ct != null ) { - String incoming = ct.getContentType() ; - - if ( matchContentType(ctSPARQLQuery, ct) ) { - mustHaveQueryParam = false ; - // Drop through. - } else if ( matchContentType(ctHTMLForm, ct)) { - // Nothing specific to do - } - else - ServletOps.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 ) - ServletOps.errorBadRequest("SPARQL Query: No 'query=' parameter") ; - if ( N > 1 ) - ServletOps.errorBadRequest("SPARQL Query: Multiple 'query=' parameters") ; - - // application/sparql-query does not use a query param. - String queryStr = request.getParameter(HttpNames.paramQuery) ; - - if ( queryStr == null ) - ServletOps.errorBadRequest("SPARQL Query: No query specified (no 'query=' found)") ; - if ( queryStr.isEmpty() ) - ServletOps.errorBadRequest("SPARQL Query: Empty query string") ; - } - - if ( params != null ) { - Enumeration<String> en = request.getParameterNames() ; - for (; en.hasMoreElements();) { - String name = en.nextElement() ; - if ( !params.contains(name) ) - ServletOps.warning(action, "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) { - ServletOps.errorOccurred(ex) ; - } - execute(queryString, action) ; - } - - private void execute(String queryString, HttpAction action) { - String queryStringLog = ServletOps.formatForLog(queryString) ; - if ( action.verbose ) - action.log.info(format("[%d] Query = \n%s", action.id, queryString)) ; - else - action.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, QueryParseBase, Syntax.syntaxARQ) ; - queryStringLog = formatForLog(query) ; - validateQuery(action, query) ; - } catch (ActionErrorException ex) { - throw ex ; - } catch (QueryParseException ex) { - ServletOps.errorBadRequest("Parse error: \n" + queryString + "\n\r" + messageForQueryException(ex)) ; - } - // Should not happen. - catch (QueryException ex) { - ServletOps.errorBadRequest("Error: \n" + queryString + "\n\r" + ex.getMessage()) ; - } - - // Assumes finished whole thing by end of sendResult. - try { - action.beginRead() ; - 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 (QueryParseException ex) { - // Late stage static error (e.g. bad fixed Lucene query string). - ServletOps.errorBadRequest("Query parse error: \n" + queryString + "\n\r" + messageForQueryException(ex)) ; - } - catch (QueryCancelledException ex) { - // Additional counter information. - incCounter(action.getEndpoint().getCounters(), QueryTimeouts) ; - throw ex ; - } finally { action.endRead() ; } - } - - /** - * Check the query - if unacceptable, throw ActionErrorException or call - * super.error - * @param action HTTP Action - * @param query SPARQL Query - */ - protected abstract void validateQuery(HttpAction action, Query query) ; - - /** Create the {@link QueryExecution} for this operation. - * @param query - * @param dataset - * @return QueryExecution - */ - protected QueryExecution createQueryExecution(Query query, Dataset dataset) { - return QueryExecutionFactory.create(query, dataset) ; - } - - /** Perform the {@link QueryExecution} once. - * @param action - * @param queryExecution - * @param query - * @param queryStringLog Informational string created from the initial query. - * @return - */ - protected SPARQLResult executeQuery(HttpAction action, QueryExecution queryExecution, Query query, String queryStringLog) { - setAnyTimeouts(queryExecution, action) ; - - if ( query.isSelectType() ) { - ResultSet rs = queryExecution.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) ; - - action.log.info(format("[%d] exec/select", action.id)) ; - return new SPARQLResult(rs) ; - } - - if ( query.isConstructType() ) { - Model model = queryExecution.execConstruct() ; - action.log.info(format("[%d] exec/construct", action.id)) ; - return new SPARQLResult(model) ; - } - - if ( query.isDescribeType() ) { - Model model = queryExecution.execDescribe() ; - action.log.info(format("[%d] exec/describe", action.id)) ; - return new SPARQLResult(model) ; - } - - if ( query.isAskType() ) { - boolean b = queryExecution.execAsk() ; - action.log.info(format("[%d] exec/ask", action.id)) ; - return new SPARQLResult(b) ; - } - - ServletOps.errorBadRequest("Unknown query type - " + queryStringLog) ; - return null ; - } - - private void setAnyTimeouts(QueryExecution qexec, HttpAction action) { -// if ( !(action.getDataService().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.getDataService().maximumTimeoutOverride, desiredTimeout) ; - if ( desiredTimeout != Long.MAX_VALUE ) - qexec.setTimeout(desiredTimeout) ; - } - - /** Choose the dataset for this SPARQL Query request. - * @param action - * @param query Query - this may be modified to remove a DatasetDescription. - * @param queryStringLog - * @return {@link Dataset} - */ - protected abstract Dataset decideDataset(HttpAction action, Query query, String queryStringLog) ; - - /** Ship the results to the remote caller. - * @param action - * @param result - * @param qPrologue - */ - 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 - ServletOps.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) ; - } +package org.apache.jena.fuseki.servlets; + +import static java.lang.String.format; +import static org.apache.jena.fuseki.server.CounterName.QueryTimeouts; +import static org.apache.jena.riot.WebContent.ctHTMLForm; +import static org.apache.jena.riot.WebContent.ctSPARQLQuery; +import static org.apache.jena.riot.WebContent.isHtmlForm; +import static org.apache.jena.riot.WebContent.matchContentType; +import static org.apache.jena.riot.web.HttpNames.paramAccept; +import static org.apache.jena.riot.web.HttpNames.paramCallback; +import static org.apache.jena.riot.web.HttpNames.paramDefaultGraphURI; +import static org.apache.jena.riot.web.HttpNames.paramForceAccept; +import static org.apache.jena.riot.web.HttpNames.paramNamedGraphURI; +import static org.apache.jena.riot.web.HttpNames.paramOutput1; +import static org.apache.jena.riot.web.HttpNames.paramOutput2; +import static org.apache.jena.riot.web.HttpNames.paramQuery; +import static org.apache.jena.riot.web.HttpNames.paramQueryRef; +import static org.apache.jena.riot.web.HttpNames.paramStyleSheet; +import static org.apache.jena.riot.web.HttpNames.paramTimeout; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.io.IndentedLineBuffer; +import org.apache.jena.atlas.web.AcceptList; +import org.apache.jena.atlas.web.ContentType; +import org.apache.jena.atlas.web.MediaType; +import org.apache.jena.fuseki.DEF; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiException; +import org.apache.jena.fuseki.FusekiLib; +import org.apache.jena.fuseki.conneg.WebLib; +import org.apache.jena.query.Dataset; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryCancelledException; +import org.apache.jena.query.QueryException; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QueryFactory; +import org.apache.jena.query.QueryParseException; +import org.apache.jena.query.ResultSet; +import org.apache.jena.query.Syntax; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.web.HttpNames; +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; + +/** + * Handle SPARQL Query requests overt eh SPARQL Protocol. Subclasses provide + * this algorithm with the actual dataset to query, whether a dataset hosted by + * this server ({@link SPARQL_QueryDataset}) or speciifed in the protocol + * request ({@link SPARQL_QueryGeneral}). + */ +public abstract class SPARQL_Query extends SPARQL_Protocol { + private static final String QueryParseBase = Fuseki.BaseParserSPARQL; + + 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); + + // POST application/x-www-form-url + // POST ?query= and no Content-Type + if (ct == null || isHtmlForm(ct)) { + // validation checked that if no Content-type, then its a POST with + // ?query= + executeWithParameter(action); + return; + } + + // POST application/sparql-query + if (matchContentType(ct, ctSPARQLQuery)) { + executeBody(action); + return; + } + + ServletOps.error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, + "Bad content type: " + ct.getContentType()); + } + + // All the params we support + + protected static List<String> allParams = Arrays.asList(paramQuery, + paramDefaultGraphURI, paramNamedGraphURI, paramQueryRef, + paramStyleSheet, paramAccept, paramOutput1, paramOutput2, + paramCallback, paramForceAccept, paramTimeout); + + /** + * Validate the request, checking HTTP method and HTTP Parameters. + * + * @param action + * HTTP Action + */ + @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)) + ServletOps.errorMethodNotAllowed("Not a GET or POST request"); + + if (HttpNames.METHOD_GET.equals(method) + && action.request.getQueryString() == null) { + ServletOps.warning(action, "Service Description / SPARQL Query / " + + action.request.getRequestURI()); + ServletOps.errorNotFound("Service Description: " + + action.request.getRequestURI()); + } + + // Use of the dataset describing parameters is check later. + try { + validateParams(action, 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(HttpAction action, Collection<String> params) { + HttpServletRequest request = action.request; + ContentType ct = FusekiLib.getContentType(request); + boolean mustHaveQueryParam = true; + if (ct != null) { + String incoming = ct.getContentType(); + + if (matchContentType(ctSPARQLQuery, ct)) { + mustHaveQueryParam = false; + // Drop through. + } else if (matchContentType(ctHTMLForm, ct)) { + // Nothing specific to do + } else + ServletOps.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) + ServletOps + .errorBadRequest("SPARQL Query: No 'query=' parameter"); + if (N > 1) + ServletOps + .errorBadRequest("SPARQL Query: Multiple 'query=' parameters"); + + // application/sparql-query does not use a query param. + String queryStr = request.getParameter(HttpNames.paramQuery); + + if (queryStr == null) + ServletOps + .errorBadRequest("SPARQL Query: No query specified (no 'query=' found)"); + if (queryStr.isEmpty()) + ServletOps.errorBadRequest("SPARQL Query: Empty query string"); + } + + if (params != null) { + Enumeration<String> en = request.getParameterNames(); + for (; en.hasMoreElements();) { + String name = en.nextElement(); + if (!params.contains(name)) + ServletOps.warning(action, + "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) { + ServletOps.errorOccurred(ex); + } + execute(queryString, action); + } + + private void execute(String queryString, HttpAction action) { + String queryStringLog = ServletOps.formatForLog(queryString); + if (action.verbose) + action.log + .info(format("[%d] Query = \n%s", action.id, queryString)); + else + action.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, QueryParseBase, + Syntax.syntaxARQ); + queryStringLog = formatForLog(query); + validateQuery(action, query); + } catch (ActionErrorException ex) { + throw ex; + } catch (QueryParseException ex) { + ServletOps.errorBadRequest("Parse error: \n" + queryString + "\n\r" + + messageForQueryException(ex)); + } + // Should not happen. + catch (QueryException ex) { + ServletOps.errorBadRequest("Error: \n" + queryString + "\n\r" + + ex.getMessage()); + } + + // Assumes finished whole thing by end of sendResult. + try { + action.beginRead(); + 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 (QueryParseException ex) { + // Late stage static error (e.g. bad fixed Lucene query string). + ServletOps.errorBadRequest("Query parse error: \n" + queryString + + "\n\r" + messageForQueryException(ex)); + } catch (QueryCancelledException ex) { + // Additional counter information. + incCounter(action.getEndpoint().getCounters(), QueryTimeouts); + throw ex; + } finally { + action.endRead(); + } + } + + /** + * Check the query - if unacceptable, throw ActionErrorException or call + * super.error + * + * @param action + * HTTP Action + * @param query + * SPARQL Query + */ + protected abstract void validateQuery(HttpAction action, Query query); + + /** + * Create the {@link QueryExecution} for this operation. + * + * @param query + * @param dataset + * @return QueryExecution + */ + protected QueryExecution createQueryExecution(Query query, Dataset dataset) { + return QueryExecutionFactory.create(query, dataset); + } + + /** + * Perform the {@link QueryExecution} once. + * + * @param action + * @param queryExecution + * @param query + * @param queryStringLog + * Informational string created from the initial query. + * @return + */ + protected SPARQLResult executeQuery(HttpAction action, + QueryExecution queryExecution, Query query, String queryStringLog) { + setAnyTimeouts(queryExecution, action); + + if (query.isSelectType()) { + ResultSet rs = queryExecution.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) ; + + action.log.info(format("[%d] exec/select", action.id)); + return new SPARQLResult(rs); + } + + if (query.isConstructType()) { + + MediaType rdfMediaType = AcceptList.match( DEF.pureRdfOffer, new AcceptList( WebLib.getAccept(action.getRequest()))); + + if ( ! rdfMediaType.getType().equals("*") ) { + Model model = queryExecution.execConstruct(); + action.log.info(format("[%d] exec/construct/model", action.id)); + return new SPARQLResult(model); + } else { + Dataset dataset = queryExecution.execConstructDataset(); + action.log + .info(format("[%d] exec/construct/dataset", action.id)); + return new SPARQLResult(dataset); + } + } + + if (query.isDescribeType()) { + Model model = queryExecution.execDescribe(); + action.log.info(format("[%d] exec/describe", action.id)); + return new SPARQLResult(model); + } + + if (query.isAskType()) { + boolean b = queryExecution.execAsk(); + action.log.info(format("[%d] exec/ask", action.id)); + return new SPARQLResult(b); + } + + ServletOps.errorBadRequest("Unknown query type - " + queryStringLog); + return null; + } + + private void setAnyTimeouts(QueryExecution qexec, HttpAction action) { + // if ( !(action.getDataService().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.getDataService().maximumTimeoutOverride, + // desiredTimeout) ; + if (desiredTimeout != Long.MAX_VALUE) + qexec.setTimeout(desiredTimeout); + } + + /** + * Choose the dataset for this SPARQL Query request. + * + * @param action + * @param query + * @param queryStringLog + * @return {@link Dataset} + */ + protected abstract Dataset decideDataset(HttpAction action, Query query, + String queryStringLog); + + /** + * Ship the results to the remote caller. + * + * @param action + * @param result + * @param qPrologue + */ + 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 if (result.isDataset()) { + ResponseDataset.doResponseDataset(action, result.getDataset()); + } else + ServletOps.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); + } } http://git-wip-us.apache.org/repos/asf/jena/blob/0eb28d80/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestQuery.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestQuery.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestQuery.java index 2f4d73a..911d105 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestQuery.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestQuery.java @@ -16,140 +16,136 @@ * limitations under the License. */ -package org.apache.jena.fuseki ; +package org.apache.jena.fuseki; -import static org.apache.jena.fuseki.ServerTest.gn1 ; -import static org.apache.jena.fuseki.ServerTest.gn2 ; -import static org.apache.jena.fuseki.ServerTest.model1 ; -import static org.apache.jena.fuseki.ServerTest.model2 ; -import static org.apache.jena.fuseki.ServerTest.serviceQuery ; -import static org.apache.jena.fuseki.ServerTest.serviceREST ; +import static org.apache.jena.fuseki.ServerTest.gn1; +import static org.apache.jena.fuseki.ServerTest.model1; +import static org.apache.jena.fuseki.ServerTest.model2; +import static org.apache.jena.fuseki.ServerTest.serviceQuery; +import static org.apache.jena.fuseki.ServerTest.serviceREST; -import java.io.IOException ; -import java.net.HttpURLConnection ; -import java.net.URL ; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Iterator; -import org.apache.jena.atlas.junit.BaseTest ; -import org.apache.jena.graph.Node ; -import org.apache.jena.query.* ; -import org.apache.jena.sparql.core.Var ; -import org.apache.jena.sparql.engine.binding.Binding ; -import org.apache.jena.sparql.resultset.ResultSetCompare ; -import org.apache.jena.sparql.sse.Item ; -import org.apache.jena.sparql.sse.SSE ; -import org.apache.jena.sparql.sse.builders.BuilderResultSet ; -import org.apache.jena.sparql.util.Convert ; -import org.junit.AfterClass ; -import org.junit.Assert ; -import org.junit.BeforeClass ; -import org.junit.Test ; +import org.apache.jena.atlas.junit.BaseTest; +import org.apache.jena.graph.Triple; +import org.apache.jena.query.DatasetAccessor; +import org.apache.jena.query.DatasetAccessorFactory; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QueryFactory; +import org.apache.jena.query.ResultSet; +import org.apache.jena.query.ResultSetFormatter; +import org.apache.jena.query.Syntax; +import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.core.Var; +import org.apache.jena.sparql.engine.binding.Binding; +import org.apache.jena.sparql.resultset.ResultSetCompare; +import org.apache.jena.sparql.sse.Item; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sparql.sse.builders.BuilderResultSet; +import org.apache.jena.sparql.util.Convert; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; -public class TestQuery extends BaseTest { - protected static ResultSet rs1 = null ; +public class TestQuery extends BaseTest +{ + protected static ResultSet rs1 = null ; static { Item item = SSE.parseItem("(resultset (?s ?p ?o) (row (?s <x>)(?p <p>)(?o 1)))") ; rs1 = BuilderResultSet.build(item) ; } - - @BeforeClass - public static void beforeClass() { + + @BeforeClass public static void beforeClass() + { ServerTest.allocServer() ; ServerTest.resetServer() ; DatasetAccessor du = DatasetAccessorFactory.createHTTP(serviceREST) ; du.putModel(model1) ; du.putModel(gn1, model2) ; } - - @AfterClass - public static void afterClass() { + + @AfterClass public static void afterClass() + { DatasetAccessor du = DatasetAccessorFactory.createHTTP(serviceREST) ; du.deleteDefault() ; ServerTest.freeServer() ; } - - @Test - public void query_01() { + + @Test public void query_01() + { execQuery("SELECT * {?s ?p ?o}", 1) ; } - - @Test - public void query_recursive_01() { - String query = "SELECT * WHERE { SERVICE <" + serviceQuery + "> { ?s ?p ?o . BIND(?o AS ?x) } }" ; - try (QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, query)) { - ResultSet rs = qExec.execSelect() ; - Var x = Var.alloc("x") ; + + @Test public void query_recursive_01() + { + String query = "SELECT * WHERE { SERVICE <" + serviceQuery + "> { ?s ?p ?o . BIND(?o AS ?x) } }"; + try ( QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, query) ) { + ResultSet rs = qExec.execSelect(); + Var x = Var.alloc("x"); while (rs.hasNext()) { - Binding b = rs.nextBinding() ; - Assert.assertNotNull(b.get(x)) ; + Binding b = rs.nextBinding(); + Assert.assertNotNull(b.get(x)); } } } - - @Test - public void query_with_params_01() { - String query = "ASK { }" ; - try (QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery + "?output=json", query)) { - boolean result = qExec.execAsk() ; - Assert.assertTrue(result) ; + + @Test public void query_with_params_01() + { + String query = "ASK { }"; + try ( QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery + "?output=json", query) ) { + boolean result = qExec.execAsk(); + Assert.assertTrue(result); } } + + @Test public void query_construct_quad_01() + { + String queryString = " CONSTRUCT { GRAPH <http://eg/g> {?s ?p ?oq} } WHERE {?s ?p ?oq}" ; + Query query = QueryFactory.create(queryString, Syntax.syntaxARQ); + + try ( QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, query) ) { + Iterator<Quad> result = qExec.execConstructQuads(); + Assert.assertTrue(result.hasNext()); + Assert.assertEquals( "http://eg/g", result.next().getGraph().getURI()); - @Test - public void request_id_header_01() throws IOException { - String qs = Convert.encWWWForm("ASK{}") ; - URL u = new URL(serviceQuery + "?query=" + qs) ; - HttpURLConnection conn = (HttpURLConnection)u.openConnection() ; - Assert.assertTrue(conn.getHeaderField("Fuseki-Request-ID") != null) ; - } - - @Test - public void query_dynamic_dataset_01() { - DatasetAccessor du = DatasetAccessorFactory.createHTTP(serviceREST) ; - du.putModel(model1); - du.putModel(gn1, model2); - { - String query = "SELECT * { ?s ?p ?o }" ; - try (QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery + "?output=json", query)) { - ResultSet rs = qExec.execSelect() ; - Node o = rs.next().getLiteral("o").asNode() ; - Node n = SSE.parseNode("1") ; - assertEquals(n, o) ; - } - } - { - - String query = "SELECT * FROM <" + gn1 + "> { ?s ?p ?o }" ; - try (QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery + "?output=json", query)) { - ResultSet rs = qExec.execSelect() ; - Node o = rs.next().getLiteral("o").asNode() ; - Node n = SSE.parseNode("2") ; - assertEquals(n, o) ; - } } } - @Test - public void query_dynamic_dataset_02() { - DatasetAccessor du = DatasetAccessorFactory.createHTTP(serviceREST) ; - du.putModel(model1); - du.putModel(gn1, model1); - du.putModel(gn2, model2); - String query = "SELECT * FROM <"+gn1+"> FROM <"+gn2+"> { ?s ?p ?o }" ; - try (QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery + "?output=json", query)) { - ResultSet rs = qExec.execSelect() ; - int n = ResultSetFormatter.consume(rs) ; - assertEquals(2, n) ; + @Test public void query_construct_01() + { + String query = " CONSTRUCT {?s ?p ?o} WHERE {?s ?p ?o}" ; + try ( QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, query) ) { + Iterator<Triple> result = qExec.execConstructTriples(); + Assert.assertTrue(result.hasNext()); } } + - private void execQuery(String queryString, int exceptedRowCount) { + + @Test public void request_id_header_01() throws IOException + { + String qs = Convert.encWWWForm("ASK{}") ; + URL u = new URL(serviceQuery+"?query="+qs); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + Assert.assertTrue(conn.getHeaderField("Fuseki-Request-ID") != null); + } + + private void execQuery(String queryString, int exceptedRowCount) + { QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, queryString) ; ResultSet rs = qExec.execSelect() ; int x = ResultSetFormatter.consume(rs) ; assertEquals(exceptedRowCount, x) ; } - - private void execQuery(String queryString, ResultSet expectedResultSet) { + + private void execQuery(String queryString, ResultSet expectedResultSet) + { QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, queryString) ; ResultSet rs = qExec.execSelect() ; boolean b = ResultSetCompare.equalsByTerm(rs, expectedResultSet) ;
