/*
* Copyright (c) 2010, University of Bristol
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2) Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3) Neither the name of the University of Bristol nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package org.ilrt.mca.rest.resources;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.ResultSetFormatter;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.sdb.SDBFactory;
import com.sun.jersey.api.view.Viewable;
import com.sun.jersey.spi.container.servlet.WebConfig;
import com.sun.jersey.spi.resource.Singleton;
import com.talis.rdfwriters.json.JSONJenaWriter;
import org.apache.log4j.Logger;
import org.ilrt.mca.RdfMediaType;
import org.ilrt.mca.rdf.StoreWrapper;
import org.ilrt.mca.rest.ex.BadRequestException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.OutputStream;
/**
* <p>This JAX-RS resource provides an entry point for receiving SPARQL queries from
* clients. These client might send specific accept header types, but it also
* might be a web browser that allows a user to send a query via a web form.</p>
* <p>The SPARQL endpoint needs to be enabled via a context parameter:</p>
* <code>
* <context-param>
* <description>Availability of the SPARQL endpoint</description>
* <param-name>sparqlEnabled</param-name>
* <param-value>${sparqlEnabled}</param-value>
* </context-param>
* </code>
*
* @author Mike Jones (mike.a.jones@bristol.ac.uk)
*/
@Singleton
@Path("/sparql")
public class SparqlEndpointResource extends AbstractResource {
// ---------- Constructors
public SparqlEndpointResource() {
super();
}
// ---------- Public methods (Jersey)
/**
* This is the entry point for running SPARQL queries via a web form. It isn't very pretty. :-)
* If there is no query then a form is displayed.
*
* @param query the SPARQL query.
* @param type the results format (json or xml).
* @return a JAX-RS response.
*/
@GET
@Produces(MediaType.TEXT_HTML)
public Response htmlView(@QueryParam("query") String query, @QueryParam("type") String type) {
if (isEndpointEnabled())
return unavailable();
if (query == null || query.equals(""))
return Response.ok(new Viewable("/admin/sparqlForm", null)).build();
return query(query, type);
}
/**
* This is an entry point for other clients, such as curl and Glint.
*
* @param query the SPARQL query.
* @return a JX-RS response.
*/
@GET
@Produces({RdfMediaType.APPLICATION_RDF_XML, RdfMediaType.SPARQL_RESULTS_JSON,
RdfMediaType.SPARQL_RESULTS_XML, RdfMediaType.TEXT_RDF_N3})
public Response query(@QueryParam("query") String query) {
if (isEndpointEnabled())
return unavailable();
// return a 404 if no query is provided
if (query == null || query.equals("")) {
logger.info("There is no request, throw an exception");
throw new BadRequestException("No query string is provided");
}
return query(query, "");
}
// ---------- Private helper methods
/**
* Queries the data sent via a client. It will throw an error of there is no query
* string or it doesn't recognise the query type.
*
* @param query the SPARQL query.
* @param type the type (json or xml via web form, or empty via another client.
* @return a JAX-RS response..
*/
private Response query(String query, String type) {
logger.info("Querying the data");
// return a 404 if no query is provided
if (query == null || query.equals(""))
throw new BadRequestException("No query string is provided");
Query q = QueryFactory.create(query);
if (q.isUnknownType()) {
throw new BadRequestException("Unexpected query");
}
return Response.ok(new SparqlQueryResults(manager.getStoreWrapper(), query, type)).build();
}
/**
* Indicates if the endpoint is enabled.
*
* @return whether the endpoint is enabled (true) or disabled (false).
*/
private boolean isEndpointEnabled() {
String enabled = wc.getServletContext().getInitParameter("sparqlEnabled");
return enabled == null || enabled.equals("false");
}
/**
* @return a JAX-RS response that says the endpoint is unavailable.
*/
private Response unavailable() {
return Response.status(Response.Status.SERVICE_UNAVAILABLE)
.entity("The SPARQL endpoint is unavailable").type(MediaType.TEXT_PLAIN)
.build();
}
@Context
private
WebConfig wc;
private final String xml = "xml";
private final Logger logger = Logger.getLogger(SparqlEndpointResource.class);
/**
* An inner class for serializing results from SPARQL queries to clients.
*/
public class SparqlQueryResults {
/**
* @param wrapper wrapper that provides access to the data store.
* @param query the SPARQL query.
* @param type (json or xml) from web form or empty string from other clients.
*/
public SparqlQueryResults(StoreWrapper wrapper, String query, String type) {
this.wrapper = wrapper;
this.query = query;
this.type = type;
}
/**
* Executes and streams the results with a JAX-RS response.
*
* @param outputStream stream for writing data.
* @param mediaType the media client that Jersey says was requested.
* @throws IOException if it all goes terribly wrong.
*/
public void executeAndStreamResults(OutputStream outputStream, MediaType mediaType)
throws IOException {
try {
// prepare the query and get access to the data
Query q = QueryFactory.create(query);
Dataset dataset = SDBFactory.connectDataset(wrapper.getStore());
// execute the query
QueryExecution qe = QueryExecutionFactory.create(q, dataset);
if (q.isAskType()) {
if (mediaType.equals(RdfMediaType.SPARQL_RESULTS_JSON_TYPE) ||
type.equals("json")) {
ResultSetFormatter.outputAsJSON(outputStream, qe.execAsk());
} else {
ResultSetFormatter.outputAsXML(outputStream, qe.execAsk());
}
} else if (q.isDescribeType()) {
Model m = qe.execDescribe();
streamModel(m, mediaType, outputStream);
m.close();
} else if (q.isConstructType()) {
Model m = qe.execConstruct();
streamModel(m, mediaType, outputStream);
m.close();
} else if (q.isSelectType()) {
if (mediaType.equals(RdfMediaType.SPARQL_RESULTS_JSON_TYPE) ||
type.equals("json")) {
ResultSetFormatter.outputAsJSON(outputStream, qe.execSelect());
} else {
ResultSetFormatter.outputAsXML(outputStream, qe.execSelect());
}
}
outputStream.flush();
qe.close();
dataset.close();
wrapper.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Stream a Jena model to the client.
*
* @param m the model that represents the results.
* @param mediaType the media type the client preferred.
* @param outputStream the stream to write the results.
*/
private void streamModel(Model m, MediaType mediaType, OutputStream outputStream) {
if (type == null || type.equals("")) {
if (mediaType.getType().equals("text") && mediaType.getSubtype().startsWith("n3")) {
m.write(outputStream, "N3");
} else {
m.write(outputStream, "RDF/XML-ABBREV");
}
} else {
if (type.equals(xml)) {
m.write(outputStream, "RDF/XML-ABBREV");
} else {
JSONJenaWriter jsonJenaWriter = new JSONJenaWriter();
jsonJenaWriter.write(m, outputStream, null);
}
}
}
private final StoreWrapper wrapper;
private final String query;
private final String type;
}
}