blob: 504441321294724bfead342e8a0e43e705232917 [file] [log] [blame]
package org.apache.cloud.rdf.web.sail;
/*
* 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.
*/
import static org.apache.rya.api.RdfCloudTripleStoreConstants.VALUE_FACTORY;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.apache.rya.api.RdfCloudTripleStoreConfiguration;
import org.apache.rya.api.log.LogUtils;
import org.apache.rya.api.security.SecurityProvider;
import org.apache.rya.rdftriplestore.RdfCloudTripleStoreConnection;
import org.apache.rya.rdftriplestore.utils.RdfFormatUtils;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.GraphQuery;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.QueryResultHandlerException;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResultHandler;
import org.eclipse.rdf4j.query.TupleQueryResultHandlerException;
import org.eclipse.rdf4j.query.Update;
import org.eclipse.rdf4j.query.UpdateExecutionException;
import org.eclipse.rdf4j.query.parser.ParsedGraphQuery;
import org.eclipse.rdf4j.query.parser.ParsedOperation;
import org.eclipse.rdf4j.query.parser.ParsedTupleQuery;
import org.eclipse.rdf4j.query.parser.ParsedUpdate;
import org.eclipse.rdf4j.query.parser.QueryParserUtil;
import org.eclipse.rdf4j.query.resultio.sparqljson.SPARQLResultsJSONWriter;
import org.eclipse.rdf4j.query.resultio.sparqlxml.SPARQLResultsXMLWriter;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFHandler;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.rdfxml.RDFXMLWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Class RdfController
* Date: Mar 7, 2012
* Time: 11:07:19 AM
*/
@Controller
public class RdfController {
private static final Logger log = Logger.getLogger(RdfController.class);
private static final int QUERY_TIME_OUT_SECONDS = 120;
@Autowired
SailRepository repository;
@Autowired
SecurityProvider provider;
@RequestMapping(value = "/queryrdf", method = {RequestMethod.GET, RequestMethod.POST})
public void queryRdf(@RequestParam("query") final String query,
@RequestParam(value = RdfCloudTripleStoreConfiguration.CONF_QUERY_AUTH, required = false) String auth,
@RequestParam(value = RdfCloudTripleStoreConfiguration.CONF_CV, required = false) final String vis,
@RequestParam(value = RdfCloudTripleStoreConfiguration.CONF_INFER, required = false) final String infer,
@RequestParam(value = "nullout", required = false) final String nullout,
@RequestParam(value = RdfCloudTripleStoreConfiguration.CONF_RESULT_FORMAT, required = false) final String emit,
@RequestParam(value = "padding", required = false) final String padding,
@RequestParam(value = "callback", required = false) final String callback,
final HttpServletRequest request,
final HttpServletResponse response) {
// WARNING: if you add to the above request variables,
// Be sure to validate and encode since they come from the outside and could contain odd damaging character sequences.
SailRepositoryConnection conn = null;
final Thread queryThread = Thread.currentThread();
auth = StringUtils.arrayToCommaDelimitedString(provider.getUserAuths(request));
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
log.debug("interrupting");
queryThread.interrupt();
}
}, QUERY_TIME_OUT_SECONDS * 1000);
try {
final ServletOutputStream os = response.getOutputStream();
conn = repository.getConnection();
final Boolean isBlankQuery = StringUtils.isEmpty(query);
final ParsedOperation operation = QueryParserUtil.parseOperation(QueryLanguage.SPARQL, query, null);
final Boolean requestedCallback = !StringUtils.isEmpty(callback);
final Boolean requestedFormat = !StringUtils.isEmpty(emit);
if (!isBlankQuery) {
if (operation instanceof ParsedGraphQuery) {
// Perform Graph Query
final RDFHandler handler = new RDFXMLWriter(os);
response.setContentType("text/xml");
performGraphQuery(query, conn, auth, infer, nullout, handler);
} else if (operation instanceof ParsedTupleQuery) {
// Perform Tuple Query
TupleQueryResultHandler handler;
if (requestedFormat && emit.equalsIgnoreCase("json")) {
handler = new SPARQLResultsJSONWriter(os);
response.setContentType("application/json");
} else {
handler = new SPARQLResultsXMLWriter(os);
response.setContentType("text/xml");
}
performQuery(query, conn, auth, infer, nullout, handler);
} else if (operation instanceof ParsedUpdate) {
// Perform Update Query
performUpdate(query, conn, os, infer, vis);
} else {
throw new MalformedQueryException("Cannot process query. Query type not supported.");
}
}
if (requestedCallback) {
os.print(")");
}
} catch (final Exception e) {
log.error("Error running query", e);
throw new RuntimeException(e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (final RepositoryException e) {
log.error("Error closing connection", e);
}
}
}
timer.cancel();
}
private void performQuery(final String query, final RepositoryConnection conn, final String auth, final String infer, final String nullout, final TupleQueryResultHandler handler) throws RepositoryException, MalformedQueryException, QueryEvaluationException, TupleQueryResultHandlerException {
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
if (auth != null && auth.length() > 0) {
tupleQuery.setBinding(RdfCloudTripleStoreConfiguration.CONF_QUERY_AUTH, VALUE_FACTORY.createLiteral(auth));
}
if (infer != null && infer.length() > 0) {
tupleQuery.setBinding(RdfCloudTripleStoreConfiguration.CONF_INFER, VALUE_FACTORY.createLiteral(Boolean.parseBoolean(infer)));
}
if (nullout != null && nullout.length() > 0) {
//output nothing, but still run query
tupleQuery.evaluate(new TupleQueryResultHandler() {
@Override
public void startQueryResult(final List<String> strings) throws TupleQueryResultHandlerException {
}
@Override
public void endQueryResult() throws TupleQueryResultHandlerException {
}
@Override
public void handleSolution(final BindingSet bindings) throws TupleQueryResultHandlerException {
}
@Override
public void handleBoolean(final boolean arg0) throws QueryResultHandlerException {
}
@Override
public void handleLinks(final List<String> arg0) throws QueryResultHandlerException {
}
});
} else {
final CountingTupleQueryResultHandlerWrapper sparqlWriter = new CountingTupleQueryResultHandlerWrapper(handler);
final long startTime = System.currentTimeMillis();
tupleQuery.evaluate(sparqlWriter);
log.info(String.format("Query Time = %.3f Result Count = %s\n",
(System.currentTimeMillis() - startTime) / 1000.,
sparqlWriter.getCount()));
}
}
private void performGraphQuery(final String query, final RepositoryConnection conn, final String auth, final String infer, final String nullout, final RDFHandler handler) throws RepositoryException, MalformedQueryException, QueryEvaluationException, RDFHandlerException {
final GraphQuery graphQuery = conn.prepareGraphQuery(QueryLanguage.SPARQL, query);
if (auth != null && auth.length() > 0) {
graphQuery.setBinding(RdfCloudTripleStoreConfiguration.CONF_QUERY_AUTH, VALUE_FACTORY.createLiteral(auth));
}
if (infer != null && infer.length() > 0) {
graphQuery.setBinding(RdfCloudTripleStoreConfiguration.CONF_INFER, VALUE_FACTORY.createLiteral(Boolean.parseBoolean(infer)));
}
if (nullout != null && nullout.length() > 0) {
//output nothing, but still run query
// TODO this seems like a strange use case.
graphQuery.evaluate(new RDFHandler() {
@Override
public void startRDF() throws RDFHandlerException {
}
@Override
public void endRDF() throws RDFHandlerException {
}
@Override
public void handleNamespace(final String prefix, final String uri)
throws RDFHandlerException {
}
@Override
public void handleStatement(final Statement st)
throws RDFHandlerException {
}
@Override
public void handleComment(final String comment)
throws RDFHandlerException {
}
});
} else {
final long startTime = System.currentTimeMillis();
graphQuery.evaluate(handler);
log.info(String.format("Query Time = %.3f\n", (System.currentTimeMillis() - startTime) / 1000.));
}
}
private void performUpdate(final String query, final SailRepositoryConnection conn, final ServletOutputStream os, final String infer, final String vis) throws RepositoryException, MalformedQueryException, IOException {
final Update update = conn.prepareUpdate(QueryLanguage.SPARQL, query);
if (infer != null && infer.length() > 0) {
update.setBinding(RdfCloudTripleStoreConfiguration.CONF_INFER, VALUE_FACTORY.createLiteral(Boolean.parseBoolean(infer)));
}
if (conn.getSailConnection() instanceof RdfCloudTripleStoreConnection && vis != null) {
final RdfCloudTripleStoreConnection<?> sailConnection = (RdfCloudTripleStoreConnection<?>) conn.getSailConnection();
sailConnection.getConf().set(RdfCloudTripleStoreConfiguration.CONF_CV, vis);
}
final long startTime = System.currentTimeMillis();
try {
update.execute();
} catch (final UpdateExecutionException e) {
final String message = "Update could not be successfully completed for query: ";
os.print(String.format(message + "%s\n\n", StringEscapeUtils.escapeHtml4(query)));
log.error(message + LogUtils.clean(query), e);
}
log.info(String.format("Update Time = %.3f\n", (System.currentTimeMillis() - startTime) / 1000.));
}
private static final class CountingTupleQueryResultHandlerWrapper implements TupleQueryResultHandler {
private final TupleQueryResultHandler indir;
private int count = 0;
public CountingTupleQueryResultHandlerWrapper(final TupleQueryResultHandler indir){
this.indir = indir;
}
public int getCount() {
return count;
}
@Override
public void endQueryResult() throws TupleQueryResultHandlerException {
indir.endQueryResult();
}
@Override
public void handleSolution(final BindingSet bindingSet) throws TupleQueryResultHandlerException {
count++;
indir.handleSolution(bindingSet);
}
@Override
public void startQueryResult(final List<String> bindingNames) throws TupleQueryResultHandlerException {
count = 0;
indir.startQueryResult(bindingNames);
}
@Override
public void handleBoolean(final boolean arg0) throws QueryResultHandlerException {
}
@Override
public void handleLinks(final List<String> arg0) throws QueryResultHandlerException {
}
}
@RequestMapping(value = "/loadrdf", method = RequestMethod.POST)
public void loadRdf(@RequestParam(required = false) final String format,
@RequestParam(value = RdfCloudTripleStoreConfiguration.CONF_CV, required = false) final String cv,
@RequestParam(required = false) final String graph,
@RequestBody final String body,
final HttpServletResponse response)
throws RepositoryException, IOException, RDFParseException {
RDFFormat format_r = RDFFormat.RDFXML;
if (format != null) {
format_r = RdfFormatUtils.getRdfFormatFromName(format);
if (format_r == null) {
throw new RuntimeException("RDFFormat[" + format + "] not found");
}
}
// add named graph as context (if specified).
final List<Resource> contextList = new ArrayList<Resource>();
if (graph != null) {
contextList.add(VALUE_FACTORY.createIRI(graph));
}
SailRepositoryConnection conn = null;
try {
conn = repository.getConnection();
if (conn.getSailConnection() instanceof RdfCloudTripleStoreConnection && cv != null) {
final RdfCloudTripleStoreConnection<?> sailConnection = (RdfCloudTripleStoreConnection<?>) conn.getSailConnection();
sailConnection.getConf().set(RdfCloudTripleStoreConfiguration.CONF_CV, cv);
}
conn.add(new StringReader(body), "", format_r, contextList.toArray(new Resource[contextList.size()]));
conn.commit();
} finally {
if (conn != null) {
conn.close();
}
}
}
}