JENA-1150: Better dispatch for actions directed on the dataset URL. To include SPARQL Update.
Project: http://git-wip-us.apache.org/repos/asf/jena/repo Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/8e7f737f Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/8e7f737f Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/8e7f737f Branch: refs/heads/master Commit: 8e7f737fa0d71ceed002282c4eca7fc3a5d843ea Parents: 0f48af3 Author: Andy Seaborne <[email protected]> Authored: Sat Feb 27 17:37:11 2016 +0000 Committer: Andy Seaborne <[email protected]> Committed: Sat Feb 27 17:40:32 2016 +0000 ---------------------------------------------------------------------- .../java/org/apache/jena/riot/web/HttpOp.java | 109 +++++++++++-- .../fuseki/servlets/SPARQL_UberServlet.java | 98 +++++++----- .../java/org/apache/jena/fuseki/TS_Fuseki.java | 1 + .../apache/jena/fuseki/TestHttpOperations.java | 158 +++++++++++++++++++ 4 files changed, 314 insertions(+), 52 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jena/blob/8e7f737f/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java ---------------------------------------------------------------------- diff --git a/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java b/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java index 67bcf87..32a9224 100644 --- a/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java +++ b/jena-arq/src/main/java/org/apache/jena/riot/web/HttpOp.java @@ -497,6 +497,22 @@ public class HttpOp { } /** + * Execute a HTTP POST and return the typed return stream. + * + * @param url + * URL + * @param contentType + * Content Type to POST + * @param content + * Content to POST + * @param acceptType + * Accept Type + */ + public static TypedInputStream execHttpPostStream(String url, String contentType, String content, String acceptType) { + return execHttpPostStream(url, contentType, content, acceptType, null, null, null) ; + } + + /** * Executes a HTTP POST with a string as the request body and response * handling * @@ -514,10 +530,24 @@ public class HttpOp { * HTTP Authenticator */ public static void execHttpPost(String url, String contentType, String content, HttpClient httpClient, - HttpContext httpContext, HttpAuthenticator authenticator) { + HttpContext httpContext, HttpAuthenticator authenticator) { execHttpPost(url, contentType, content, null, nullHandler, httpClient, httpContext, authenticator); } + public static TypedInputStream execHttpPostStream(String url, String contentType, String content, String acceptType, + HttpClient httpClient, HttpContext httpContext, HttpAuthenticator authenticator) { + CaptureInput handler = new CaptureInput(); + try { + execHttpPost(url, contentType, content, acceptType, handler, httpClient, httpContext, authenticator); + } catch (HttpException ex) { + if (ex.getResponseCode() == HttpSC.NOT_FOUND_404) + return null; + throw ex; + } + return handler.get(); + } + + /** * Executes a HTTP POST with a string as the request body and response * handling @@ -552,6 +582,19 @@ public class HttpOp { } } + // +// +// StringEntity e = null; +// try { +// e = new StringEntity(content, StandardCharsets.UTF_8); +// e.setContentType(contentType); +// return execHttpPostStream(url, e, acceptType, null, null, null) ; +// } +// finally { +// closeEntity(e); +// } +// } + /** * Executes a HTTP POST with a request body from an input stream without * response body with no response handling @@ -646,6 +689,20 @@ public class HttpOp { } /** + * Execute a HTTP POST and return the typed return stream. + * + * @param url + * URL + * @param entity + * Entity to POST + */ + public static TypedInputStream execHttpPostStream(String url, HttpEntity entity, String acceptHeader) { + CaptureInput handler = new CaptureInput(); + execHttpPost(url, entity, acceptHeader, handler); + return handler.get() ; + } + + /** * Executes a HTTP Post * * @param url @@ -700,6 +757,34 @@ public class HttpOp { * Entity to POST * @param acceptHeader * Accept Header + * @param httpClient + * HTTP Client + * @param httpContext + * HTTP Context + * @param authenticator + * HTTP Authenticator + */ + public static TypedInputStream execHttpPostStream(String url, HttpEntity entity, String acceptHeader, + HttpClient httpClient, HttpContext httpContext, HttpAuthenticator authenticator) { + CaptureInput handler = new CaptureInput(); + execHttpPost(url, entity, acceptHeader, handler, httpClient, httpContext, authenticator) ; + return handler.get() ; + } + + /** + * POST with response body. + * <p> + * The content for the POST body comes from the HttpEntity. + * <p> + * Additional headers e.g. for authentication can be injected through an + * {@link HttpContext} + * + * @param url + * URL + * @param entity + * Entity to POST + * @param acceptHeader + * Accept Header * @param handler * Response handler called to process the response * @param httpClient @@ -717,35 +802,39 @@ public class HttpOp { httppost.setEntity(entity); exec(url, httppost, acceptHeader, handler, httpClient, httpContext, authenticator); } - + + + // ---- HTTP POST as a form. /** - * Executes a HTTP POST and returns a TypedInputStream, The TypedInputStream - * must be closed. + * Executes a HTTP POST. * * @param url * URL * @param params * Parameters to POST - * @param acceptHeader */ - public static TypedInputStream execHttpPostFormStream(String url, Params params, String acceptHeader) { - return execHttpPostFormStream(url, params, acceptHeader, null, null, null); + public static void execHttpPostForm(String url, Params params) { + execHttpPostForm(url, params, null, nullHandler); } /** - * Executes a HTTP POST. + * Executes a HTTP POST and returns a TypedInputStream, The TypedInputStream + * must be closed. * * @param url * URL * @param params * Parameters to POST + * @param acceptHeader */ - public static void execHttpPostForm(String url, Params params) { - execHttpPostForm(url, params, null, nullHandler); + public static TypedInputStream execHttpPostFormStream(String url, Params params, String acceptHeader) { + return execHttpPostFormStream(url, params, acceptHeader, null, null, null); } + + // @formatter:off // /** // * Executes a HTTP POST Form. http://git-wip-us.apache.org/repos/asf/jena/blob/8e7f737f/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java index 7b116c3..2b41487 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java @@ -118,7 +118,7 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL private final ActionSPARQL uploadServlet = new SPARQL_Upload() ; private final ActionSPARQL gspServlet_R = new SPARQL_GSP_R() ; private final ActionSPARQL gspServlet_RW = new SPARQL_GSP_RW() ; - private final ActionSPARQL restQuads_R = new REST_Quads_R() ; // XXX + private final ActionSPARQL restQuads_R = new REST_Quads_R() ; private final ActionSPARQL restQuads_RW = new REST_Quads_RW() ; public SPARQL_UberServlet() { super(); } @@ -153,6 +153,10 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL */ @Override protected void executeAction(HttpAction action) { + + //SPARQL Update direct + //SPARQL Query POST + long id = action.id ; HttpServletRequest request = action.request ; HttpServletResponse response = action.response ; @@ -171,28 +175,70 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL boolean hasParams = request.getParameterMap().size() > 0 ; + // Is it a query or update because of a ?query= , ?request= parameter? // Test for parameters - includes HTML forms. - boolean hasParamQuery = request.getParameter(HttpNames.paramQuery) != null ; + boolean isQuery = request.getParameter(HttpNames.paramQuery) != null ; // Include old name "request=" - boolean hasParamUpdate = request.getParameter(HttpNames.paramUpdate) != null || request.getParameter(HttpNames.paramRequest) != null ; + boolean isUpdate = request.getParameter(HttpNames.paramUpdate) != null || request.getParameter(HttpNames.paramRequest) != null ; + boolean hasParamGraph = request.getParameter(HttpNames.paramGraph) != null ; boolean hasParamGraphDefault = request.getParameter(HttpNames.paramGraphDefault) != null ; + boolean hasTrailing = ( trailing.length() != 0 ) ; + String ct = request.getContentType() ; String charset = request.getCharacterEncoding() ; MediaType mt = null ; - if ( ct != null ) + if ( ct != null ) { + // Parse it. mt = MediaType.create(ct, charset) ; - + // Another way to send queries and updates is with the content-type. + if ( contentTypeSPARQLQuery.equalsIgnoreCase(ct) ) + isQuery = true ; + else if ( contentTypeSPARQLUpdate.equalsIgnoreCase(ct) ) + isUpdate = true ; + } + if (action.log.isInfoEnabled() ) { //String cxt = action.getContextPath() ; action.log.info(format("[%d] %s %s :: '%s' :: %s ? %s", id, method, desc.getName(), trailing, (mt==null?"<none>":mt), (qs==null?"":qs))) ; } + + if ( !hasTrailing ) { + // Nothing after the DataAccessPoint i.e. Dataset by name. + // Action on the dataset itself. This can be: + // http://localhost:3030/ds?query= + // http://localhost:3030/ds and a content type. + // http://localhost:3030/ds?default ?graph= GSP + // http://localhost:3030/ds , REST quads action on the dataset itself. + if ( isQuery ) { + if ( !allowQuery(action) ) + ServletOps.errorMethodNotAllowed("SPARQL query : "+method) ; + executeRequest(action, queryServlet) ; + return ; + } - boolean hasTrailing = ( trailing.length() != 0 ) ; + if ( isUpdate ) { + // SPARQL Update + if ( !allowUpdate(action) ) + ServletOps.errorMethodNotAllowed("SPARQL update : "+method) ; + // This wil dela with using GET. + executeRequest(action, updateServlet) ; + return ; + } + + // ?graph=, ?default + if ( hasParamGraph || hasParamGraphDefault ) { + doGraphStoreProtocol(action) ; + return ; + } - if ( !hasTrailing && !hasParams ) { + if ( hasParams ) { + // Unrecognized ?key=value + ServletOps.errorBadRequest("Malformed request") ; + } + // REST dataset. boolean isGET = method.equals(HttpNames.METHOD_GET) ; boolean isHEAD = method.equals(HttpNames.METHOD_HEAD) ; @@ -206,7 +252,7 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL return ; } // If the read-only server has the same name as the writable server, - // and the default fro a read-only server is "/data", like a writable dataset, + // and the default for a read-only server is "/data", like a writable dataset, // this test is insufficient. if ( allowREST_W(action) ) restQuads_RW.executeLifecycle(action) ; @@ -215,40 +261,8 @@ public abstract class SPARQL_UberServlet extends ActionSPARQL return ; } - if ( !hasTrailing ) { - boolean isPOST = action.getRequest().getMethod().equals(HttpNames.METHOD_POST) ; - // Nothing after the DataAccessPoint i.e Dataset by name. - // e.g. http://localhost:3030/ds?query= - // Query - GET or POST. - // Query - ?query= or body of application/sparql-query - if ( hasParamQuery || ( isPOST && contentTypeSPARQLQuery.equalsIgnoreCase(ct) ) ) { - // SPARQL Query - if ( !allowQuery(action) ) - ServletOps.errorMethodNotAllowed("SPARQL query : "+method) ; - executeRequest(action, queryServlet) ; - return ; - } - - // Insist on POST for update. - // Update - ?update= or body of application/sparql-update - if ( isPOST && ( hasParamUpdate || contentTypeSPARQLUpdate.equalsIgnoreCase(ct) ) ) { - // SPARQL Update - if ( !allowUpdate(action) ) - ServletOps.errorMethodNotAllowed("SPARQL update : "+method) ; - executeRequest(action, updateServlet) ; - return ; - } - - // ?graph=, ?default - if ( hasParamGraph || hasParamGraphDefault ) { - doGraphStoreProtocol(action) ; - return ; - } - - ServletOps.errorBadRequest("Malformed request") ; - ServletOps.errorMethodNotAllowed("SPARQL Graph Store Protocol : "+method) ; - } - + // Has trailing path name => service or direct naming GSP. + final boolean checkForPossibleService = true ; if ( checkForPossibleService && action.getEndpoint() != null ) { // There is a trailing part. http://git-wip-us.apache.org/repos/asf/jena/blob/8e7f737f/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java index 2094755..cdbb629 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java @@ -34,6 +34,7 @@ import org.junit.runners.Suite ; @Suite.SuiteClasses( { TestHttpOp.class , TestSPARQLProtocol.class + , TestHttpOperations.class , TestHttpOptions.class , TestDatasetGraphAccessorHTTP.class , TestDatasetAccessorHTTP.class http://git-wip-us.apache.org/repos/asf/jena/blob/8e7f737f/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOperations.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOperations.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOperations.java new file mode 100644 index 0000000..5042576 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestHttpOperations.java @@ -0,0 +1,158 @@ +/* + * 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 ; + +import static org.apache.jena.fuseki.ServerTest.* ; +import static org.apache.jena.fuseki.ServerTest.serviceUpdate ; + +import org.apache.jena.atlas.junit.BaseTest ; +import org.apache.jena.atlas.web.HttpException ; +import org.apache.jena.atlas.web.TypedInputStream ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.riot.web.HttpOp ; +import org.apache.jena.sparql.engine.http.Params ; +import org.apache.jena.sparql.util.Convert ; +import org.junit.AfterClass ; +import org.junit.Assert ; +import org.junit.BeforeClass ; +import org.junit.Test ; + +/** Operation by HTTP - test dispatch - lower level than TestSPARQLProtocol */ +public class TestHttpOperations extends BaseTest { + @BeforeClass + public static void beforeClass() { + ServerTest.allocServer() ; + } + + @AfterClass + public static void afterClass() { + ServerTest.freeServer() ; + } + + // XXX and directly on dataset + + @Test + public void query_by_get_1() { + String qs = Convert.encWWWForm("ASK{}") ; + String u = serviceQuery+"?query=" + qs ; + try (TypedInputStream in = HttpOp.execHttpGet(u)) { + Assert.assertNotNull(in); + } + } + + @Test + public void query_by_post_1() { + String u = serviceQuery ; + try (TypedInputStream in = HttpOp.execHttpPostStream(u, WebContent.contentTypeSPARQLQuery, "ASK{}", "*")) { + Assert.assertNotNull(in); + } + } + + @Test + public void query_by_post_2() { + String qs = Convert.encWWWForm("ASK{}") ; + String u = serviceQuery+"?query=" + qs ; + try (TypedInputStream in = HttpOp.execHttpPostStream(u, null, null)) { + Assert.assertNotNull(in); + } + } + + @Test + public void query_by_form_1() { + String qs = Convert.encWWWForm("ASK{}") ; + String u = serviceQuery ; + Params params = new Params(); + params.addParam("query", "ASK{}") ; + try (TypedInputStream in = HttpOp.execHttpPostFormStream(u, params, "*") ) { + Assert.assertNotNull(in); + } + } + + @Test(expected=HttpException.class) + public void query_by_form_2() { + String u = serviceQuery ; + Params params = new Params(); + params.addParam("foobar", "ASK{}") ; // Wrong. + try (TypedInputStream in = HttpOp.execHttpPostFormStream(u, params, "*") ) { + Assert.assertNotNull(in); + } + } + + @Test + public void update_by_post_1() { + String u = serviceUpdate ; + HttpOp.execHttpPost(u, WebContent.contentTypeSPARQLUpdate, "INSERT DATA{}") ; + } + + // POST ?request= :: Not supported. +// @Test +// public void update_by_post_2() { +// String us = Convert.encWWWForm("INSERT DATA {}") ; +// String u = serviceUpdate+"?update=" + us ; +// try (TypedInputStream in = HttpOp.execHttpPostStream(u, null, null)) { +// Assert.assertNotNull(in); +// } +// } + + @Test + public void update_by_form_1() { + String u = serviceUpdate ; + Params params = new Params(); + params.addParam("update", "INSERT DATA{}") ; + try (TypedInputStream in = HttpOp.execHttpPostFormStream(u, params, "*") ) { + Assert.assertNotNull(in); + } + } + + @Test(expected=HttpException.class) + public void update_by_form_2() { + String u = serviceUpdate ; + Params params = new Params(); + params.addParam("query", "INSERT DATA{}") ; // Wrong paramater + try (TypedInputStream in = HttpOp.execHttpPostFormStream(u, params, "*") ) { + Assert.assertNotNull(in); + } + } + + // ---- Dataset direct + + @Test + public void ds_query_by_get_1() { + String qs = Convert.encWWWForm("ASK{}") ; + String u = urlDataset ; + try (TypedInputStream in = HttpOp.execHttpGet(u)) { + Assert.assertNotNull(in); + } + } + + @Test + public void ds_query_by_post_1() { + String u = urlDataset ; + try (TypedInputStream in = HttpOp.execHttpPostStream(u, WebContent.contentTypeSPARQLQuery, "ASK{}", "*")) { + Assert.assertNotNull(in); + } + } + + @Test + public void ds_update_by_post_1() { + String u = urlDataset ; + HttpOp.execHttpPost(u, WebContent.contentTypeSPARQLUpdate, "INSERT DATA{}") ; + } + +}
