package de.unikiel.inf.comsys.neo4j.http; /* * #%L * neo4j-sparql-extension * %% * Copyright (C) 2014 Niclas Hoyer * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import de.unikiel.inf.comsys.neo4j.SPARQLExtensionProps; import de.unikiel.inf.comsys.neo4j.http.streams.SPARQLBooleanStreamingOutput; import de.unikiel.inf.comsys.neo4j.http.streams.SPARQLGraphStreamingOutput; import de.unikiel.inf.comsys.neo4j.http.streams.SPARQLTupleStreamingOutput; import de.unikiel.inf.comsys.neo4j.inference.QueryRewriter; import de.unikiel.inf.comsys.neo4j.inference.QueryRewriterFactory; import java.nio.charset.Charset; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.Variant; import org.openrdf.query.BooleanQuery; import org.openrdf.query.GraphQuery; import org.openrdf.query.MalformedQueryException; import org.openrdf.query.Query; import org.openrdf.query.QueryLanguage; import org.openrdf.query.TupleQuery; import org.openrdf.query.resultio.BooleanQueryResultWriterFactory; import org.openrdf.query.resultio.TupleQueryResultWriterFactory; import org.openrdf.query.resultio.sparqljson.SPARQLBooleanJSONWriterFactory; import org.openrdf.query.resultio.sparqljson.SPARQLResultsJSONWriterFactory; import org.openrdf.query.resultio.sparqlxml.SPARQLBooleanXMLWriterFactory; import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLWriterFactory; import org.openrdf.query.resultio.text.BooleanTextWriterFactory; import org.openrdf.query.resultio.text.csv.SPARQLResultsCSVWriterFactory; import org.openrdf.query.resultio.text.tsv.SPARQLResultsTSVWriterFactory; import org.openrdf.repository.RepositoryException; import org.openrdf.repository.sail.SailRepository; import org.openrdf.repository.sail.SailRepositoryConnection; import org.openrdf.rio.RDFWriterFactory; import org.openrdf.rio.RDFWriterRegistry; /** * Implementation of the "query operation" part of the SPARQL 1.1 Protocol * standard. * * @see <a href="http://www.w3.org/TR/sparql11-protocol/#query-operation"> * SPARQL 1.1 Protocol * </a> */ public class SPARQLQuery extends AbstractSailResource { private final List<Variant> queryResultVariants; private final List<Variant> booleanResultVariants; private final QueryRewriterFactory qwfactory; private final int timeout; /** * Create a new SPARQL 1.1 query resource based on a repository. * * @param rep the repository this resources operates on */ public SPARQLQuery(SailRepository rep) { super(rep); // initialize additional result MIME-Types queryResultVariants = Variant.mediaTypes( MediaType.valueOf(RDFMediaType.SPARQL_RESULTS_JSON), MediaType.valueOf(RDFMediaType.SPARQL_RESULTS_XML), MediaType.valueOf(RDFMediaType.SPARQL_RESULTS_CSV), MediaType.valueOf(RDFMediaType.SPARQL_RESULTS_TSV) ).add().build(); booleanResultVariants = Variant.mediaTypes( MediaType.valueOf(RDFMediaType.SPARQL_RESULTS_JSON), MediaType.valueOf(RDFMediaType.SPARQL_RESULTS_XML), MediaType.valueOf(MediaType.TEXT_PLAIN) ).add().build(); // get reference to query rewriting component this.qwfactory = QueryRewriterFactory.getInstance(rep); // get query timeout from properties String sout = SPARQLExtensionProps.getProperty("query.timeout"); this.timeout = Integer.parseInt(sout); } /** * Query via GET. * * @see <a href="http://www.w3.org/TR/sparql11-protocol/#query-operation"> * SPARQL 1.1 Protocol * </a> * @param req JAX-RS {@link Request} object * @param uriInfo JAX-RS {@link UriInfo} object * @param queryString the "query" query parameter * @param defgraphs the "default-graph-uri" query parameter * @param namedgraphs the "named-graph-uri" query parameter * @param inference the "inference" query parameter * @return the result of the SPARQL query */ @GET @Produces({ RDFMediaType.SPARQL_RESULTS_JSON, RDFMediaType.SPARQL_RESULTS_XML, RDFMediaType.SPARQL_RESULTS_CSV, RDFMediaType.SPARQL_RESULTS_TSV, RDFMediaType.RDF_TURTLE, RDFMediaType.RDF_NTRIPLES, RDFMediaType.RDF_XML, RDFMediaType.RDF_JSON }) public Response query( @Context Request req, @Context UriInfo uriInfo, @QueryParam("query") String queryString, @QueryParam("default-graph-uri") List<String> defgraphs, @QueryParam("named-graph-uri") List<String> namedgraphs, @QueryParam("inference") String inference) { return handleQuery( req, uriInfo, queryString, defgraphs, namedgraphs, inference); } /** * Query via URL-encoded POST. * * @see <a href="http://www.w3.org/TR/sparql11-protocol/#query-operation"> * SPARQL 1.1 Protocol * </a> * @param req JAX-RS {@link Request} object * @param uriInfo JAX-RS {@link UriInfo} object * @param queryString the "query" form encoded parameter * @param defgraphs the "default-graph-uri" form encoded parameter * @param namedgraphs the "named-graph-uri" form encoded parameter * @param inference the "inference" form encoded parameter * @return the result of the SPARQL query */ @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response queryPOSTEncoded( @Context Request req, @Context UriInfo uriInfo, @FormParam("query") String queryString, @FormParam("default-graph-uri") List<String> defgraphs, @FormParam("named-graph-uri") List<String> namedgraphs, @FormParam("inference") String inference) { return handleQuery( req, uriInfo, queryString, defgraphs, namedgraphs, inference); } /** * Query via POST directly. * * @see <a href="http://www.w3.org/TR/sparql11-protocol/#query-operation"> * SPARQL 1.1 Protocol * </a> * @param req JAX-RS {@link Request} object * @param uriInfo JAX-RS {@link UriInfo} object * @param defgraphs the "default-graph-uri" form encoded parameter * @param namedgraphs the "named-graph-uri" form encoded parameter * @param inference the "inference" form encoded parameter * @param queryString query as string (from HTTP request body) * @return the result of the SPARQL query */ @POST @Consumes(RDFMediaType.SPARQL_QUERY) public Response queryPOSTDirect( @Context Request req, @Context UriInfo uriInfo, @QueryParam("default-graph-uri") List<String> defgraphs, @QueryParam("named-graph-uri") List<String> namedgraphs, @QueryParam("inference") String inference, String queryString) { return handleQuery( req, uriInfo, queryString, defgraphs, namedgraphs, inference); } /** * Query via GET (with inference). * * @see <a href="http://www.w3.org/TR/sparql11-protocol/#query-operation"> * SPARQL 1.1 Protocol * </a> * @param req JAX-RS {@link Request} object * @param uriInfo JAX-RS {@link UriInfo} object * @param queryString the "query" query parameter * @param defgraphs the "default-graph-uri" query parameter * @param namedgraphs the "named-graph-uri" query parameter * @return the result of the SPARQL query */ @GET @Path("/inference") @Produces({ RDFMediaType.SPARQL_RESULTS_JSON, RDFMediaType.SPARQL_RESULTS_XML, RDFMediaType.SPARQL_RESULTS_CSV, RDFMediaType.SPARQL_RESULTS_TSV, RDFMediaType.RDF_TURTLE, RDFMediaType.RDF_NTRIPLES, RDFMediaType.RDF_XML, RDFMediaType.RDF_JSON }) public Response queryInference( @Context Request req, @Context UriInfo uriInfo, @QueryParam("query") String queryString, @QueryParam("default-graph-uri") List<String> defgraphs, @QueryParam("named-graph-uri") List<String> namedgraphs) { return handleQuery( req, uriInfo, queryString, defgraphs, namedgraphs, "true"); } /** * Query via URL-encoded POST (with inference). * * @see <a href="http://www.w3.org/TR/sparql11-protocol/#query-operation"> * SPARQL 1.1 Protocol * </a> * @param req JAX-RS {@link Request} object * @param uriInfo JAX-RS {@link UriInfo} object * @param queryString the "query" form encoded parameter * @param defgraphs the "default-graph-uri" form encoded parameter * @param namedgraphs the "named-graph-uri" form encoded parameter * @return the result of the SPARQL query */ @POST @Path("/inference") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response queryPOSTEncodedInference( @Context Request req, @Context UriInfo uriInfo, @FormParam("query") String queryString, @FormParam("default-graph-uri") List<String> defgraphs, @FormParam("named-graph-uri") List<String> namedgraphs) { return handleQuery( req, uriInfo, queryString, defgraphs, namedgraphs, "true"); } /** * Query via POST directly (with inference). * * @see <a href="http://www.w3.org/TR/sparql11-protocol/#query-operation"> * SPARQL 1.1 Protocol * </a> * @param req JAX-RS {@link Request} object * @param uriInfo JAX-RS {@link UriInfo} object * @param defgraphs the "default-graph-uri" form encoded parameter * @param namedgraphs the "named-graph-uri" form encoded parameter * @param queryString query as string (from HTTP request body) * @return the result of the SPARQL query */ @POST @Path("/inference") @Consumes(RDFMediaType.SPARQL_QUERY) public Response queryPOSTDirectInference( @Context Request req, @Context UriInfo uriInfo, @QueryParam("default-graph-uri") List<String> defgraphs, @QueryParam("named-graph-uri") List<String> namedgraphs, String queryString) { return handleQuery( req, uriInfo, queryString, defgraphs, namedgraphs, "true"); } /** * Implements the handling of a SPARQL query. * * This method accepts the different parameters for SPARQL requests, * executes the request (with optional inference) and returns the * result as JAX-RS HTTP response. The response will be streamed, so large * result sets are possible. * * @see SPARQLTupleStreamingOutput * @see SPARQLBooleanStreamingOutput * @see SPARQLGraphStreamingOutput * @param req JAX-RS {@link Request} object * @param uriInfo JAX-RS {@link UriInfo} object * @param queryString SPARQL query to execute * @param defgraphs the "default-graph-uri" query parameter * @param namedgraphs the "named-graph-uri" query parameter * @param inference true, if the results should include inferred solutions * @return the result of the SPARQL query */ private Response handleQuery( Request req, UriInfo uriInfo, String queryString, List<String> defgraphs, List<String> namedgraphs, String inference) { SailRepositoryConnection conn = null; try { // check for empty query if (queryString == null) { throw new MalformedQueryException("Missing query parameter"); } conn = getConnection(); final Query query; // check if the query should be rewritten for inference if (inference != null && inference.equals("true")) { // hand over to query rewriting component QueryRewriter qw = qwfactory.getRewriter(conn); query = qw.rewrite( QueryLanguage.SPARQL, queryString, uriInfo.getAbsolutePath().toASCIIString()); } else { // direct preparation using Sesame repository query = conn.prepareQuery( QueryLanguage.SPARQL, queryString, uriInfo.getAbsolutePath().toASCIIString()); } // limit query execution time query.setMaxQueryTime(timeout); // check query form and possible result variants final List<Variant> acceptable; boolean isGraphQuery = false; boolean isBooleanQuery = false; if (query instanceof GraphQuery) { isGraphQuery = true; } else if (query instanceof BooleanQuery) { isBooleanQuery = true; } if (isGraphQuery) { acceptable = rdfResultVariants; } else if (isBooleanQuery) { acceptable = booleanResultVariants; } else { acceptable = queryResultVariants; } final Variant variant = req.selectVariant(acceptable); // if acceptable variants does not match "Accept" header, abort if (variant == null) { return Response.notAcceptable(acceptable).build(); } final MediaType mt = variant.getMediaType(); final String mtstr = mt.getType() + "/" + mt.getSubtype(); StreamingOutput stream; // select result writer based on query form and return streaming // output if (isGraphQuery) { GraphQuery gq = (GraphQuery) query; stream = new SPARQLGraphStreamingOutput( gq, getRDFWriterFactory(mtstr), conn); } else if (isBooleanQuery) { BooleanQuery bq = (BooleanQuery) query; stream = new SPARQLBooleanStreamingOutput( bq, getBooleanWriterFactory(mtstr), conn); } else { TupleQuery tq = (TupleQuery) query; stream = new SPARQLTupleStreamingOutput( tq, getTupleWriterFactory(mtstr), conn); } return Response.ok(stream).type(mt).build(); } catch (MalformedQueryException ex) { // syntax error close(conn, ex); String str = ex.getMessage(); return Response.status(Response.Status.BAD_REQUEST).entity( str.getBytes(Charset.forName("UTF-8"))).build(); } catch (RepositoryException ex) { // server error close(conn, ex); throw new WebApplicationException(ex); } } /** * Returns a {@link RDFWriterFactory} that produces RDF data according to a * given MIME-type. * * @param mimetype the mimetype * @return the corresponding writer factory */ private RDFWriterFactory getRDFWriterFactory(String mimetype) { RDFWriterRegistry registry = RDFWriterRegistry.getInstance(); return registry.get(getRDFFormat(mimetype)); } /** * Returns a {@link TupleQueryResultWriterFactory} that returns a writer * that writes SPARQL query results in the format of a given MIME-Type. * * @param mimetype the mimetype * @return the corresponding query result writer factory */ private TupleQueryResultWriterFactory getTupleWriterFactory(String mimetype) { switch (mimetype) { default: case RDFMediaType.SPARQL_RESULTS_JSON: return new SPARQLResultsJSONWriterFactory(); case RDFMediaType.SPARQL_RESULTS_XML: return new SPARQLResultsXMLWriterFactory(); case RDFMediaType.SPARQL_RESULTS_CSV: return new SPARQLResultsCSVWriterFactory(); case RDFMediaType.SPARQL_RESULTS_TSV: return new SPARQLResultsTSVWriterFactory(); } } /** * Returns a {@link BooleanQueryResultWriterFactory} that returns a writer * that writes SPARQL query results in the format of a given MIME-Type. * * @param mimetype the mimetype * @return the corresponding query result writer factory */ private BooleanQueryResultWriterFactory getBooleanWriterFactory(String mimetype) { switch (mimetype) { default: case RDFMediaType.SPARQL_RESULTS_JSON: return new SPARQLBooleanJSONWriterFactory(); case RDFMediaType.SPARQL_RESULTS_XML: return new SPARQLBooleanXMLWriterFactory(); case MediaType.TEXT_PLAIN: return new BooleanTextWriterFactory(); } } }