Reworking CONSTRUCT for quads. GSOC JENA 491 final.
Project: http://git-wip-us.apache.org/repos/asf/jena/repo Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/077a4136 Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/077a4136 Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/077a4136 Branch: refs/heads/master Commit: 077a413662c19f8ac063e236d7bdd5f652fef3cb Parents: 3ac6a5a 602e3c0 Author: Andy Seaborne <[email protected]> Authored: Thu Aug 20 09:39:26 2015 +0100 Committer: Andy Seaborne <[email protected]> Committed: Thu Aug 20 09:39:43 2015 +0100 ---------------------------------------------------------------------- .../org/apache/jena/atlas/web/AcceptList.java | 145 ++-- .../sparql/engine/http/QueryEngineHTTP.java | 43 +- .../main/java/org/apache/jena/fuseki/DEF.java | 17 +- .../jena/fuseki/servlets/ResponseDataset.java | 20 +- .../jena/fuseki/servlets/ResponseModel.java | 5 + .../jena/fuseki/servlets/SPARQL_Query.java | 838 +++++++++---------- .../java/org/apache/jena/fuseki/TestQuery.java | 260 ++++-- 7 files changed, 699 insertions(+), 629 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jena/blob/077a4136/jena-arq/src/main/java/org/apache/jena/atlas/web/AcceptList.java ---------------------------------------------------------------------- diff --cc jena-arq/src/main/java/org/apache/jena/atlas/web/AcceptList.java index 8801016,e3eb290..1d251c8 --- a/jena-arq/src/main/java/org/apache/jena/atlas/web/AcceptList.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/web/AcceptList.java @@@ -54,27 -54,27 +54,24 @@@ public class AcceptLis * @param acceptItems */ -- public static AcceptList create(MediaType...acceptItems) -- { -- AcceptList accepList = new AcceptList() ; ++ public static AcceptList create(MediaType... acceptItems) { ++ AcceptList accepList = new AcceptList(); for ( MediaType mtype : acceptItems ) -- accepList.ranges.add(new MediaRange(mtype)) ; -- return accepList ; -- } ++ accepList.ranges.add(new MediaRange(mtype)); ++ return accepList; ++ } /** * Create a list of accept items from strings. * @param acceptStrings */ -- public static AcceptList create(String... acceptStrings) -- { -- AcceptList accepList = new AcceptList() ; -- for ( String acceptString : acceptStrings ) -- { -- accepList.ranges.add( new MediaRange( acceptString ) ); ++ public static AcceptList create(String... acceptStrings) { ++ AcceptList accepList = new AcceptList(); ++ for ( String acceptString : acceptStrings ) { ++ accepList.ranges.add(new MediaRange(acceptString)); } -- return accepList ; ++ return accepList; } /** @@@ -82,26 -82,26 +79,23 @@@ * @param headerString */ -- public AcceptList(String headerString) -- { ++ public AcceptList(String headerString) { try { -- ranges = stringToAcceptList(headerString) ; -- } catch (Exception ex) -- { -- ex.printStackTrace(System.err) ; -- Log.warn(this, "Unrecognized accept string (ignored): "+headerString) ; -- ranges = new ArrayList<>() ; ++ ranges = stringToAcceptList(headerString); ++ } ++ catch (Exception ex) { ++ ex.printStackTrace(System.err); ++ Log.warn(this, "Unrecognized accept string (ignored): " + headerString); ++ ranges = new ArrayList<>(); } } -- -- private /*public*/ boolean accepts(MediaRange aItem) -- { -- return match(aItem) != null ; ++ ++ private /* public */ boolean accepts(MediaRange aItem) { ++ return match(aItem) != null; } - private List<MediaRange> entries() - public List<MediaRange> entries() -- { -- return Collections.unmodifiableList(ranges) ; ++ public List<MediaRange> entries() { ++ return Collections.unmodifiableList(ranges); } private final static MediaRangeCompare comparator = new MediaRangeCompare() ; @@@ -119,11 -119,11 +113,9 @@@ double weight = -1 ; int exact = -1 ; -- for ( MediaRange acceptItem : ranges ) -- { -- if ( acceptItem.accepts(offer) ) -- { -- boolean newChoice = false; ++ for ( MediaRange acceptItem : ranges ) { ++ if ( acceptItem.accepts(offer) ) { ++ boolean newChoice = false; if ( choice == null ) // First possibility. newChoice = true ; @@@ -134,12 -134,12 +126,11 @@@ // New possibility has same weight but better exactness. newChoice = true ; -- if ( newChoice ) -- { -- choice = acceptItem ; -- weight = acceptItem.get_q() ; -- exact = acceptItem.subweight() ; -- continue ; ++ if ( newChoice ) { ++ choice = acceptItem; ++ weight = acceptItem.get_q(); ++ exact = acceptItem.subweight(); ++ continue; } //if ( weight == acceptItem.get_q() && !exact && } @@@ -157,19 -157,19 +148,17 @@@ * @return MediaType */ -- static public MediaType match(AcceptList proposalList, AcceptList offerList) -- { -- MediaRange cause = null ; ++ static public MediaType match(AcceptList proposalList, AcceptList offerList) { ++ MediaRange cause = null; MediaRange choice = null ; // From the proposalList double weight = -1 ; int exactness = -1 ; -- for ( MediaRange offer : offerList.entries() ) -- { -- MediaRange m = proposalList.match(offer) ; ++ for ( MediaRange offer : offerList.entries() ) { ++ MediaRange m = proposalList.match(offer); if ( m == null ) -- continue ; -- boolean newChoice = false ; ++ continue; ++ boolean newChoice = false; if ( choice == null ) newChoice = true ; @@@ -185,51 -185,51 +174,43 @@@ } } -- if ( choice == null ) return null ; return new MediaType(choice); } -- public MediaRange first() -- { -- MediaRange choice = null ; -- for ( MediaRange acceptItem : ranges ) -- { ++ public MediaRange first() { ++ MediaRange choice = null; ++ for ( MediaRange acceptItem : ranges ) { if ( choice != null && choice.get_q() >= acceptItem.get_q() ) -- continue ; -- choice = acceptItem ; ++ continue; ++ choice = acceptItem; } -- return choice ; ++ return choice; } -- ++ @Override public String toString() { return ranges.toString() ; } -- private static List<MediaRange> stringToAcceptList(String s) -- { -- List<MediaRange> ranges = new ArrayList<>() ; ++ private static List<MediaRange> stringToAcceptList(String s) { ++ List<MediaRange> ranges = new ArrayList<>(); if ( s == null ) -- return ranges ; ++ return ranges; -- String[] x = s.split(",") ; -- for ( String aX : x ) -- { -- if ( aX.equals( "" ) ) -- { ++ String[] x = s.split(","); ++ for ( String aX : x ) { ++ if ( aX.equals("") ) { continue; } -- MediaRange mType = new MediaRange( aX ); -- ranges.add( mType ); ++ MediaRange mType = new MediaRange(aX); ++ ranges.add(mType); } -- return ranges ; ++ return ranges; } -- private static class MediaRangeCompare implements Comparator<MediaRange> -- { ++ private static class MediaRangeCompare implements Comparator<MediaRange> { @Override -- public int compare(MediaRange mType1, MediaRange mType2) -- { ++ public int compare(MediaRange mType1, MediaRange mType2) { int r = Double.compare(mType1.get_q(), mType2.get_q()) ; if ( r == 0 ) @@@ -238,8 -238,8 +219,7 @@@ if ( r == 0 ) r = subCompare(mType1.getSubType(), mType2.getSubType()) ; --// if ( r == 0 ) --// { ++// if ( r == 0 ) { // // This reverses the input order so that the rightmost elements is the // // greatest and hence is the first mentioned in the accept range. // @@@ -254,19 -254,19 +234,18 @@@ return r ; } -- public int subCompare(String a, String b) -- { ++ public int subCompare(String a, String b) { if ( a == null ) -- return 1 ; ++ return 1; if ( b == null ) -- return -1 ; ++ return -1; if ( a.equals("*") && b.equals("*") ) -- return 0 ; ++ return 0; if ( a.equals("*") ) -- return -1 ; ++ return -1; if ( b.equals("*") ) -- return 1 ; -- return 0 ; ++ return 1; ++ return 0; } } } http://git-wip-us.apache.org/repos/asf/jena/blob/077a4136/jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryEngineHTTP.java ---------------------------------------------------------------------- diff --cc jena-arq/src/main/java/org/apache/jena/sparql/engine/http/QueryEngineHTTP.java index cab831f,abe4d1f..2fa9602 --- 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 @@@ -85,6 -87,9 +87,10 @@@ public class QueryEngineHTTP implement private String selectContentType = defaultSelectHeader(); private String askContentType = defaultAskHeader(); private String modelContentType = defaultConstructHeader(); + private String datasetContentType = WebContent.defaultDatasetAcceptHeader; + - private String httpResponseContentType; ++ // Received content type ++ private String httpResponseContentType = null ; /** * Supported content types for SELECT queries */ @@@ -332,7 -337,11 +338,12 @@@ this.authenticator = authenticator; } - @Override ++ /** The Content-Type response header received (null before the remote operation is attempted). */ + public String getHttpResponseContentType() { + return httpResponseContentType; + } + + @Override public ResultSet execSelect() { checkNotClosed() ; ResultSet rs = execResultSetInner() ; @@@ -355,9 -364,10 +366,10 @@@ retainedConnection = in; // This will be closed on close() retainedClient = httpQuery.shouldShutdownClient() ? httpQuery.getClient() : null; -- // Don't assume the endpoint actually gives back the content type we -- // asked for ++ // Don't assume the endpoint actually gives back the ++ // content type we asked for String actualContentType = httpQuery.getContentType(); + httpResponseContentType = actualContentType; // If the server fails to return a Content-Type then we will assume // the server returned the type we asked for @@@ -399,7 -409,20 +411,14 @@@ @Override public Dataset execConstructDataset(){ - return null; - - DatasetGraph graph = DatasetGraphFactory.createMem(); - + checkNotClosed() ; - try - { - execConstructQuads().forEachRemaining(graph::add); - } - finally - { ++ DatasetGraph dataset = DatasetGraphFactory.createMem(); ++ try { ++ execConstructQuads().forEachRemaining(dataset::add); ++ } finally { + this.close(); + } - return DatasetFactory.create(graph); - ++ return DatasetFactory.create(dataset); } @Override http://git-wip-us.apache.org/repos/asf/jena/blob/077a4136/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java ---------------------------------------------------------------------- diff --cc jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseDataset.java index e12493d,5242f4b..1464b99 --- 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 @@@ -18,7 -18,15 +18,8 @@@ package org.apache.jena.fuseki.servlets; --import static org.apache.jena.riot.WebContent.charsetUTF8; -import static org.apache.jena.riot.WebContent.contentTypeNQuads; -import static org.apache.jena.riot.WebContent.contentTypeTriG; -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 static org.apache.jena.riot.WebContent.* ; + import java.util.HashMap; import java.util.Map; @@@ -41,17 -48,31 +42,28 @@@ 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" ; + private static final String contentOutputJSONLD = "json-ld" ; + private static final String contentOutputJSONRDF = "json-rdf" ; + private static final String contentOutputJSON = "json" ; + private static final String contentOutputXML = "xml" ; + private static final String contentOutputText = "text" ; + private static final String contentOutputTTL = "ttl" ; + private static final String contentOutputNT = "nt" ; - + 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, contentTypeNQuads) ; - ResponseOps.put(shortNamesModel, contentOutputTriG, contentTypeTriG) ; + ResponseOps.put(shortNamesModel, contentOutputJSONLD, contentTypeJSONLD) ; + ResponseOps.put(shortNamesModel, contentOutputJSONRDF, contentTypeRDFJSON) ; + ResponseOps.put(shortNamesModel, contentOutputJSON, contentTypeJSONLD) ; + ResponseOps.put(shortNamesModel, contentOutputXML, contentTypeRDFXML) ; + ResponseOps.put(shortNamesModel, contentOutputText, contentTypeTurtle) ; + ResponseOps.put(shortNamesModel, contentOutputTTL, contentTypeTurtle) ; + ResponseOps.put(shortNamesModel, contentOutputNT, contentTypeNTriples) ; + ResponseOps.put(shortNamesModel, contentOutputNQuads, WebContent.contentTypeNQuads) ; + ResponseOps.put(shortNamesModel, contentOutputTriG, WebContent.contentTypeTriG) ; } public static void doResponseDataset(HttpAction action, Dataset dataset) http://git-wip-us.apache.org/repos/asf/jena/blob/077a4136/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java ---------------------------------------------------------------------- diff --cc jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java index 68cf016,0000000..e46fbe6 mode 100644,000000..100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ResponseModel.java @@@ -1,135 -1,0 +1,140 @@@ +/* + * 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 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.rdf.model.Model ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.riot.RDFLanguages ; +import static org.apache.jena.riot.WebContent.* ; +import org.apache.jena.web.HttpSC ; + ++// REPLACED by RssponseDataset ++// Kept here in case we need to revert it (Aug 2015) ++// Delete after release of Fusek 2.3.1 or earlier if specific confirmation ++// the new code is OK. ++ +public class ResponseModel +{ + // Short names for "output=" + private static final String contentOutputJSONLD = "json-ld" ; + private static final String contentOutputJSONRDF = "json-rdf" ; + private static final String contentOutputJSON = "json" ; + private static final String contentOutputXML = "xml" ; + private static final String contentOutputText = "text" ; + private static final String contentOutputTTL = "ttl" ; + private static final String contentOutputNT = "nt" ; + + public static Map<String,String> shortNamesModel = new HashMap<String, String>() ; + static { + + // Some short names. keys are lowercase. + ResponseOps.put(shortNamesModel, contentOutputJSONLD, contentTypeJSONLD) ; + ResponseOps.put(shortNamesModel, contentOutputJSONRDF, contentTypeRDFJSON) ; + ResponseOps.put(shortNamesModel, contentOutputJSON, contentTypeJSONLD) ; + ResponseOps.put(shortNamesModel, contentOutputXML, contentTypeRDFXML) ; + ResponseOps.put(shortNamesModel, contentOutputText, contentTypeTurtle) ; + ResponseOps.put(shortNamesModel, contentOutputTTL, contentTypeTurtle) ; + ResponseOps.put(shortNamesModel, contentOutputNT, contentTypeNTriples) ; + } + + public static void doResponseModel(HttpAction action, Model model) + { + HttpServletRequest request = action.request ; + HttpServletResponse response = action.response ; + + String mimeType = null ; // Header request type + + // TODO Use MediaType throughout. + MediaType i = ConNeg.chooseContentType(request, DEF.rdfOffer, DEF.acceptRDFXML) ; + 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, model, 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/077a4136/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestQuery.java ---------------------------------------------------------------------- diff --cc jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestQuery.java index 911d105,5a57da3..1c496f0 --- 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 @@@ -126,18 -187,47 +187,46 @@@ public class TestQuery extends BaseTes } } - + @Test + public void query_construct_02() + { + String query = " CONSTRUCT {?s ?p ?o} WHERE {?s ?p ?o}" ; + try ( QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, query) ) { + Model result = qExec.execConstruct(); + assertEquals(1, result.size()); + } + } - @Test public void request_id_header_01() throws IOException + @Test + public void query_construct_conneg() { - 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); + String query = " CONSTRUCT {?s ?p ?o} WHERE {?s ?p ?o}" ; + for (MediaType type: DEF.rdfOffer.entries()){ + String contentType = type.toHeaderString(); + try ( QueryEngineHTTP qExec = (QueryEngineHTTP) QueryExecutionFactory.sparqlService(serviceQuery, query) ) { + qExec.setModelContentType(initConstructContentTypes( contentType ) ); + qExec.execConstruct(); + assertEquals( contentType , qExec.getHttpResponseContentType()); + } + } } - - private void execQuery(String queryString, int exceptedRowCount) + + @Test + public void query_construct_quad_conneg() { + String queryString = " CONSTRUCT { GRAPH ?g {?s ?p ?o} } WHERE { GRAPH ?g {?s ?p ?o}}" ; + Query query = QueryFactory.create(queryString, Syntax.syntaxARQ); + for (MediaType type: DEF.quadsOffer.entries()){ + String contentType = type.toHeaderString(); + try ( QueryEngineHTTP qExec = (QueryEngineHTTP) QueryExecutionFactory.sparqlService(serviceQuery, query) ) { + qExec.setDatasetContentType(initConstructContentTypes( contentType ) ); + qExec.execConstructQuads(); + assertEquals( contentType , qExec.getHttpResponseContentType()); + } + } + } - + + private void execQuery(String queryString, int exceptedRowCount) { QueryExecution qExec = QueryExecutionFactory.sparqlService(serviceQuery, queryString) ; ResultSet rs = qExec.execSelect() ; int x = ResultSetFormatter.consume(rs) ;
