From: <tho...@us...> - 2011-04-15 14:02:31
|
Revision: 4402 http://bigdata.svn.sourceforge.net/bigdata/?rev=4402&view=rev Author: thompsonbry Date: 2011-04-15 14:02:24 +0000 (Fri, 15 Apr 2011) Log Message: ----------- Working on the NanoSparqlServer and its test suite. 1. Added support for the binary interchange type for a SPARQL result set. 2. Added support for ASK queries. 3. Documented the MIME Types which you can CONNEG using the REST API on the wiki. Note that while you can send RDF data using NQUADS and request result sets using JSON, the test suite does not cover those cases because the corresponding writer (NQUADS) or parser (JSON SPARQL results) has not been written yet. Modified Paths: -------------- branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/BigdataRDFContext.java branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/QueryServlet.java branches/QUADS_QUERY_BRANCH/bigdata-sails/src/test/com/bigdata/rdf/sail/webapp/TestNanoSparqlServer.java Modified: branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/BigdataRDFContext.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/BigdataRDFContext.java 2011-04-15 13:30:18 UTC (rev 4401) +++ branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/BigdataRDFContext.java 2011-04-15 14:02:24 UTC (rev 4402) @@ -1,8 +1,7 @@ package com.bigdata.rdf.sail.webapp; -import info.aduna.xml.XMLWriter; - import java.io.OutputStream; +import java.nio.charset.Charset; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -26,15 +25,16 @@ import org.openrdf.query.parser.ParsedQuery; import org.openrdf.query.parser.QueryParser; import org.openrdf.query.parser.sparql.SPARQLParserFactory; +import org.openrdf.query.resultio.BooleanQueryResultFormat; +import org.openrdf.query.resultio.BooleanQueryResultWriter; +import org.openrdf.query.resultio.BooleanQueryResultWriterRegistry; import org.openrdf.query.resultio.TupleQueryResultFormat; import org.openrdf.query.resultio.TupleQueryResultWriter; import org.openrdf.query.resultio.TupleQueryResultWriterRegistry; -import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLWriter; import org.openrdf.repository.RepositoryException; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.RDFWriter; import org.openrdf.rio.RDFWriterRegistry; -import org.openrdf.rio.rdfxml.RDFXMLWriter; import org.openrdf.sail.SailException; import com.bigdata.bop.BufferAnnotations; @@ -49,6 +49,7 @@ import com.bigdata.journal.RWStrategy; import com.bigdata.journal.TimestampUtility; import com.bigdata.rdf.sail.BigdataSail; +import com.bigdata.rdf.sail.BigdataSailBooleanQuery; import com.bigdata.rdf.sail.BigdataSailGraphQuery; import com.bigdata.rdf.sail.BigdataSailRepository; import com.bigdata.rdf.sail.BigdataSailRepositoryConnection; @@ -254,11 +255,18 @@ protected final String mimeType; /** - * The {@link RDFFormat} for the response (required only for queries - * which produce RDF data, as opposed to RDF result sets). + * The character encoding to use with the negotiated {@link #mimeType} + * -or- <code>null</code> (it will be <code>null</code> for a binary + * encoding). */ - protected final RDFFormat format; + protected final Charset charset; + /** + * The file extension (without the leading ".") to use with the + * negotiated {@link #mimeType}. + */ + protected final String fileExt; + /** The request. */ private final HttpServletRequest req; @@ -293,15 +301,19 @@ * @param queryStr * The SPARQL query string. * @param mimeType - * The MIME type to be used for the response. - * @param format - * The {@link RDFFormat} for the response (required only for - * queries which produce RDF data, as opposed to RDF result - * sets). + * The MIME type to be used for the response. The caller must + * verify that the MIME Type is appropriate for the query + * type. + * @param charset + * The character encoding to use with the negotiated MIME + * type (this is <code>null</code> for binary encodings). + * @param fileExt + * The file extension (without the leading ".") to use with + * that MIME Type. * @param req * The request. - * @param resp - * The response. + * @param os + * Where to write the data for the query result. */ protected AbstractQueryTask(// final String namespace,// @@ -309,17 +321,36 @@ final String queryStr,// final QueryType queryType,// final String mimeType,// - final RDFFormat format,// + final Charset charset,// + final String fileExt,// final HttpServletRequest req,// final OutputStream os// - ) { + ) { + if (namespace == null) + throw new IllegalArgumentException(); + if (queryStr == null) + throw new IllegalArgumentException(); + if (queryType == null) + throw new IllegalArgumentException(); + if (mimeType == null) + throw new IllegalArgumentException(); +// if (charset == null) // Note: null for binary encodings. +// throw new IllegalArgumentException(); + if (fileExt == null) + throw new IllegalArgumentException(); + if (req == null) + throw new IllegalArgumentException(); + if (os == null) + throw new IllegalArgumentException(); + this.namespace = namespace; this.timestamp = timestamp; this.queryStr = queryStr; this.queryType = queryType; this.mimeType = mimeType; - this.format = format; + this.charset = charset; + this.fileExt = fileExt; this.req = req; this.os = os; this.queryId = Long.valueOf(m_queryIdFactory.incrementAndGet()); @@ -390,6 +421,43 @@ } // class AbstractQueryTask + /** + * Executes a ASK query. + */ + private class AskQueryTask extends AbstractQueryTask { + + public AskQueryTask(final String namespace, final long timestamp, + final String queryStr, final QueryType queryType, + final BooleanQueryResultFormat format, + final HttpServletRequest req, final OutputStream os) { + + super(namespace, timestamp, queryStr, queryType, format + .getDefaultMIMEType(), format.getCharset(), format + .getDefaultFileExtension(), req, os); + + } + + protected void doQuery(final BigdataSailRepositoryConnection cxn, + final OutputStream os) throws Exception { + + final BigdataSailBooleanQuery query = cxn.prepareBooleanQuery( + QueryLanguage.SPARQL, queryStr, baseURI); + + // Note: getQueryTask() verifies that format will be non-null. + final BooleanQueryResultFormat format = BooleanQueryResultWriterRegistry + .getInstance().getFileFormatForMIMEType(mimeType); + + final BooleanQueryResultWriter w = BooleanQueryResultWriterRegistry + .getInstance().get(format).getWriter(os); + + final boolean result = query.evaluate(); + + w.write(result); + + } + + } + /** * Executes a tuple query. */ @@ -397,12 +465,13 @@ public TupleQueryTask(final String namespace, final long timestamp, final String queryStr, final QueryType queryType, - final String mimeType, final RDFFormat format, + final TupleQueryResultFormat format, final HttpServletRequest req, final OutputStream os) { - super(namespace, timestamp, queryStr, queryType, mimeType, format, - req, os); + super(namespace, timestamp, queryStr, queryType, format + .getDefaultMIMEType(), format.getCharset(), format + .getDefaultFileExtension(), req, os); } @@ -411,22 +480,13 @@ final BigdataSailTupleQuery query = cxn.prepareTupleQuery( QueryLanguage.SPARQL, queryStr, baseURI); - - /* - * FIXME Raise this into the query CONNEG logic parallel to how - * we handle queries which result in RDF data rather than SPARQL - * result sets. - */ + + // Note: getQueryTask() verifies that format will be non-null. final TupleQueryResultFormat format = TupleQueryResultWriterRegistry .getInstance().getFileFormatForMIMEType(mimeType); - final TupleQueryResultWriter w = format == null ? new SPARQLResultsXMLWriter( - new XMLWriter(os)) - : TupleQueryResultWriterRegistry.getInstance().get(format) - .getWriter(os); - -// final RDFWriter w = format == null ? new RDFXMLWriter(os) -// : RDFWriterRegistry.getInstance().get(format).getWriter(os); + final TupleQueryResultWriter w = TupleQueryResultWriterRegistry + .getInstance().get(format).getWriter(os); query.evaluate(w); @@ -441,12 +501,13 @@ public GraphQueryTask(final String namespace, final long timestamp, final String queryStr, final QueryType queryType, - final String mimeType, final RDFFormat format, + final RDFFormat format, final HttpServletRequest req, final OutputStream os) { - super(namespace, timestamp, queryStr, queryType, mimeType, format, - req, os); + super(namespace, timestamp, queryStr, queryType, format + .getDefaultMIMEType(), format.getCharset(), format + .getDefaultFileExtension(), req, os); } @@ -479,9 +540,13 @@ // if(true) // throw new RuntimeException(); - final RDFWriter w = format == null ? new RDFXMLWriter(os) - : RDFWriterRegistry.getInstance().get(format).getWriter(os); + // Note: getQueryTask() verifies that format will be non-null. + final RDFFormat format = RDFWriterRegistry.getInstance() + .getFileFormatForMIMEType(mimeType); + final RDFWriter w = RDFWriterRegistry.getInstance().get(format) + .getWriter(os); + query.evaluate(w); } @@ -531,8 +596,12 @@ final QueryType queryType = QueryType.fromQuery(queryStr); /* - * CONNEG for the mime type. + * CONNEG for the MIME type. * + * Note: An attempt to CONNEG for a MIME type which can not be used with + * a give type of query will result in a response using a default MIME + * Type for that query. + * * TODO This is a hack which will obey an Accept header IF the header * contains a single well-formed MIME Type. Complex accept headers will * not be matched and quality parameters (q=...) are ignored. (Sesame @@ -541,47 +610,34 @@ */ final String acceptStr = req.getHeader("Accept"); - RDFFormat format = acceptStr == null ? null : RDFFormat - .forMIMEType(acceptStr); - - final String mimeType; switch (queryType) { case ASK: { - /* - * FIXME handle ASK. - */ - break; + + final BooleanQueryResultFormat format = acceptStr == null ? null + : BooleanQueryResultFormat.forMIMEType(acceptStr, + BooleanQueryResultFormat.SPARQL); + + return new AskQueryTask(namespace, timestamp, queryStr, queryType, + format, req, os); + } case DESCRIBE: case CONSTRUCT: { - if (format != null) { + final RDFFormat format = RDFFormat.forMIMEType(acceptStr, + RDFFormat.RDFXML); - mimeType = format.getDefaultMIMEType(); + return new GraphQueryTask(namespace, timestamp, queryStr, + queryType, format, req, os); - } else { - - mimeType = BigdataRDFServlet.MIME_RDF_XML; - - } - - return new GraphQueryTask(namespace, timestamp, queryStr, - queryType, mimeType, format, req, os); } case SELECT: { - if (format != null) { + final TupleQueryResultFormat format = TupleQueryResultFormat + .forMIMEType(acceptStr, TupleQueryResultFormat.SPARQL); - mimeType = format.getDefaultMIMEType(); - - } else { - - mimeType = BigdataRDFServlet.MIME_SPARQL_RESULTS_XML; - - } - return new TupleQueryTask(namespace, timestamp, queryStr, - queryType, mimeType, format, req, os); + queryType, format, req, os); } } // switch(queryType) Modified: branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/QueryServlet.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/QueryServlet.java 2011-04-15 13:30:18 UTC (rev 4401) +++ branches/QUADS_QUERY_BRANCH/bigdata-sails/src/java/com/bigdata/rdf/sail/webapp/QueryServlet.java 2011-04-15 14:02:24 UTC (rev 4402) @@ -47,6 +47,30 @@ } /** + * Return <code>true</code> if the <code>Content-disposition</code> header + * should be set to indicate that the response body should be handled as an + * attachment rather than presented inline. This is just a hint to the user + * agent. How the user agent handles this hint is up to it. + * + * @param mimeType + * The mime type. + * + * @return <code>true</code> if it should be handled as an attachment. + */ + private boolean isAttachment(final String mimeType) { + if(mimeType.equals(MIME_TEXT_PLAIN)) { + return false; + } else if(mimeType.equals(MIME_SPARQL_RESULTS_XML)) { + return false; + } else if(mimeType.equals(MIME_SPARQL_RESULTS_JSON)) { + return false; + } else if(mimeType.equals(MIME_APPLICATION_XML)) { + return false; + } + return true; + } + + /** * Run a SPARQL query. * * FIXME Does not handle default-graph-uri or named-graph-uri query @@ -95,53 +119,25 @@ resp.setStatus(HTTP_OK); - // Figure out the filename extension for the response. - - final String ext; - final String charset; - - if(queryTask.format != null) { + resp.setContentType(queryTask.mimeType); - /* - * If some RDFormat was negotiated, then construct the filename - * for the attachment using the default extension for that - * format and the queryId. - */ - - ext = queryTask.format.getDefaultFileExtension(); - - charset = queryTask.format.getCharset().name(); + if (queryTask.charset != null) { - } else { - - if(queryTask.mimeType.equals(MIME_SPARQL_RESULTS_XML)) { - - // See http://www.w3.org/TR/rdf-sparql-XMLres/ - - ext = "srx"; // Sparql Result Set. - - } else if(queryTask.mimeType.equals(MIME_SPARQL_RESULTS_JSON)) { - - // See http://www.w3.org/TR/rdf-sparql-json-res/ - - ext = "srj"; - - } else { - - ext = "xxx"; - - } - - charset = QueryServlet.charset; + // Note: Binary encodings do not specify charset. + resp.setCharacterEncoding(queryTask.charset.name()); } - - resp.setContentType(queryTask.mimeType); - - resp.setCharacterEncoding(charset); - resp.setHeader("Content-disposition", "attachment; filename=query" - + queryTask.queryId + "." + ext); + if (isAttachment(queryTask.mimeType)) { + /* + * Mark this as an attachment (rather than inline). This is just + * a hint to the user agent. How the user agent handles this + * hint is up to it. + */ + resp.setHeader("Content-disposition", + "attachment; filename=query" + queryTask.queryId + "." + + queryTask.fileExt); + } if(TimestampUtility.isCommitTime(queryTask.timestamp)) { Modified: branches/QUADS_QUERY_BRANCH/bigdata-sails/src/test/com/bigdata/rdf/sail/webapp/TestNanoSparqlServer.java =================================================================== --- branches/QUADS_QUERY_BRANCH/bigdata-sails/src/test/com/bigdata/rdf/sail/webapp/TestNanoSparqlServer.java 2011-04-15 13:30:18 UTC (rev 4401) +++ branches/QUADS_QUERY_BRANCH/bigdata-sails/src/test/com/bigdata/rdf/sail/webapp/TestNanoSparqlServer.java 2011-04-15 14:02:24 UTC (rev 4402) @@ -33,8 +33,14 @@ import org.openrdf.model.vocabulary.RDFS; import org.openrdf.query.BindingSet; import org.openrdf.query.TupleQueryResultHandlerBase; +import org.openrdf.query.resultio.BooleanQueryResultFormat; +import org.openrdf.query.resultio.BooleanQueryResultParser; +import org.openrdf.query.resultio.BooleanQueryResultParserFactory; +import org.openrdf.query.resultio.BooleanQueryResultParserRegistry; +import org.openrdf.query.resultio.TupleQueryResultFormat; import org.openrdf.query.resultio.TupleQueryResultParser; -import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLParserFactory; +import org.openrdf.query.resultio.TupleQueryResultParserFactory; +import org.openrdf.query.resultio.TupleQueryResultParserRegistry; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.RDFHandlerException; import org.openrdf.rio.RDFParser; @@ -65,15 +71,13 @@ * * @todo Test default-graph-uri(s) and named-graph-uri(s). * - * @todo Verify conneg for various mime type for different kinds of queries. - * E.g., conneg for json result sets for SELECT, conneg for n3 response - * for CONSTRUCT, etc. The logic for handling Accept headers does not pay - * attention to q=... parameters, so only a single mime type should be - * specified in the Accept header. - * - * @todo NQUADS RDFWriter needs to be written. Then we can test NQUADS + * @todo An NQUADS RDFWriter needs to be written. Then we can test NQUADS * interchange. * + * @todo A SPARQL result sets JSON parser needs to be written (Sesame bundles a + * writer, but not a parser) before we can test queries which CONNEG for a + * JSON result set. + * * @todo Add tests for SIDS mode interchange of RDF XML. * * @todo The methods which return a mutation count should verify the returned @@ -247,7 +251,7 @@ public String defaultGraphUri = null; /** The accept header. */ public String acceptHeader = // - BigdataRDFServlet.MIME_SPARQL_RESULTS_JSON + ";q=1" + // + BigdataRDFServlet.MIME_SPARQL_RESULTS_XML + ";q=1" + // "," + // RDFFormat.RDFXML.getDefaultMIMEType() + ";q=1"// ; @@ -356,10 +360,6 @@ */ protected Graph buildGraph(final HttpURLConnection conn) throws Exception { -// System.err.println(getResponseBody(conn)); - - final Graph g = new GraphImpl(); - try { final String baseURI = ""; @@ -380,6 +380,8 @@ fail("RDFParserFactory not found: Content-Type=" + contentType + ", format=" + format); + final Graph g = new GraphImpl(); + final RDFParser rdfParser = factory.getParser(); rdfParser.setValueFactory(new ValueFactoryImpl()); @@ -405,6 +407,52 @@ } + /** + * Parse a SPARQL result set for an ASK query. + * + * @param conn + * The connection from which to read the results. + * + * @return <code>true</code> or <code>false</code> depending on what was + * encoded in the SPARQL result set. + * + * @throws Exception + * If anything goes wrong, including if the result set does not + * encode a single boolean value. + */ + protected boolean askResults(final HttpURLConnection conn) throws Exception { + + try { + + final String contentType = conn.getContentType(); + + final BooleanQueryResultFormat format = BooleanQueryResultFormat + .forMIMEType(contentType); + + if (format == null) + fail("No format for Content-Type: " + contentType); + + final BooleanQueryResultParserFactory factory = BooleanQueryResultParserRegistry + .getInstance().get(format); + + if (factory == null) + fail("No factory for Content-Type: " + contentType); + + final BooleanQueryResultParser parser = factory.getParser(); + + final boolean result = parser.parse(conn.getInputStream()); + + return result; + + } finally { + + // terminate the http connection. + conn.disconnect(); + + } + + } + /** * Counts the #of results in a SPARQL result set. * @@ -418,12 +466,26 @@ */ protected long countResults(final HttpURLConnection conn) throws Exception { - final AtomicLong nsolutions = new AtomicLong(); + try { - try { + final String contentType = conn.getContentType(); - final TupleQueryResultParser parser = new SPARQLResultsXMLParserFactory().getParser(); + final TupleQueryResultFormat format = TupleQueryResultFormat + .forMIMEType(contentType); + if (format == null) + fail("No format for Content-Type: " + contentType); + + final TupleQueryResultParserFactory factory = TupleQueryResultParserRegistry + .getInstance().get(format); + + if (factory == null) + fail("No factory for Content-Type: " + contentType); + + final TupleQueryResultParser parser = factory.getParser(); + + final AtomicLong nsolutions = new AtomicLong(); + parser.setTupleQueryResultHandler(new TupleQueryResultHandlerBase() { // Indicates the end of a sequence of solutions. public void endQueryResult() { @@ -460,7 +522,7 @@ } /** - * Select everything in the kb using a GET. + * Issue a "status" request against the service. */ public void test_STATUS() throws Exception { @@ -509,9 +571,50 @@ } - /** - * Select everything in the kb using a GET. - */ + /** + * "ASK" query using GET with an empty KB. + */ + public void test_GET_ASK() throws Exception { + + final String queryStr = "ASK where {?s ?p ?o}"; + + final QueryOptions opts = new QueryOptions(); + opts.serviceURL = m_serviceURL; + opts.queryStr = queryStr; + opts.method = "GET"; + + opts.acceptHeader = BooleanQueryResultFormat.SPARQL.getDefaultMIMEType(); + assertEquals(false, askResults(doSparqlQuery(opts, requestPath))); + + opts.acceptHeader = BooleanQueryResultFormat.TEXT.getDefaultMIMEType(); + assertEquals(false, askResults(doSparqlQuery(opts, requestPath))); + + } + + /** + * "ASK" query using POST with an empty KB. + */ + public void test_POST_ASK() throws Exception { + + final String queryStr = "ASK where {?s ?p ?o}"; + + final QueryOptions opts = new QueryOptions(); + opts.serviceURL = m_serviceURL; + opts.queryStr = queryStr; + opts.method = "POST"; + + opts.acceptHeader = BooleanQueryResultFormat.SPARQL.getDefaultMIMEType(); + assertEquals(false, askResults(doSparqlQuery(opts, requestPath))); + + opts.acceptHeader = BooleanQueryResultFormat.TEXT.getDefaultMIMEType(); + assertEquals(false, askResults(doSparqlQuery(opts, requestPath))); + + } + + /** + * Select everything in the kb using a GET. There will be no solutions + * (assuming that we are using a told triple kb or quads kb w/o axioms). + */ public void test_GET_SELECT_ALL() throws Exception { final String queryStr = "select * where {?s ?p ?o}"; @@ -521,17 +624,21 @@ opts.queryStr = queryStr; opts.method = "GET"; - // No solutions (assuming a told triple kb or quads kb w/o axioms). + opts.acceptHeader = TupleQueryResultFormat.SPARQL.getDefaultMIMEType(); assertEquals(0, countResults(doSparqlQuery(opts, requestPath))); - // Now with json. - opts.acceptHeader = BigdataRDFServlet.MIME_SPARQL_RESULTS_JSON; - assertEquals(0, countResults(doSparqlQuery(opts, requestPath))); + // TODO JSON parser is not bundled by openrdf. +// opts.acceptHeader = TupleQueryResultFormat.JSON.getDefaultMIMEType(); +// assertEquals(0, countResults(doSparqlQuery(opts, requestPath))); + opts.acceptHeader = TupleQueryResultFormat.BINARY.getDefaultMIMEType(); + assertEquals(0, countResults(doSparqlQuery(opts, requestPath))); + } /** - * Select everything in the kb using a POST. + * Select everything in the kb using a POST. There will be no solutions + * (assuming that we are using a told triple kb or quads kb w/o axioms). */ public void test_POST_SELECT_ALL() throws Exception { @@ -542,11 +649,14 @@ opts.queryStr = queryStr; opts.method = "POST"; - // No solutions (assuming a told triple kb or quads kb w/o axioms). + opts.acceptHeader = TupleQueryResultFormat.SPARQL.getDefaultMIMEType(); assertEquals(0, countResults(doSparqlQuery(opts, requestPath))); - // Now with json. - opts.acceptHeader = BigdataRDFServlet.MIME_SPARQL_RESULTS_JSON; + // TODO JSON parser is not bundled by openrdf. +// opts.acceptHeader = TupleQueryResultFormat.JSON.getDefaultMIMEType(); +// assertEquals(0, countResults(doSparqlQuery(opts, requestPath))); + + opts.acceptHeader = TupleQueryResultFormat.BINARY.getDefaultMIMEType(); assertEquals(0, countResults(doSparqlQuery(opts, requestPath))); } @@ -1198,11 +1308,4 @@ } - // FIXME test ASK. - public void test_ASK() throws Exception { - - fail("Write unit test for ASK"); - - } - } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |