xyz.ottr.lutra.bottr.source.AbstractSPARQLSource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lutra-bottr Show documentation
Show all versions of lutra-bottr Show documentation
This Lutra submodule implements bOTTR (https://spec.ottr.xyz/bOTTR/0.1/). bOTTR is a mapping language for transforming
data from relational databases, triplestores, CSV-files and other sources, into instances of templates.
Lutra is the reference implementation of the OTTR framework. For more information about OTTR, see http://ottr.xyz.
package xyz.ottr.lutra.bottr.source;
/*-
* #%L
* lutra-bottr
* %%
* Copyright (C) 2018 - 2019 University of Oslo
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.shared.JenaException;
import org.apache.jena.shared.PrefixMapping;
import xyz.ottr.lutra.bottr.BOTTR;
import xyz.ottr.lutra.bottr.model.ArgumentMap;
import xyz.ottr.lutra.bottr.model.ArgumentMaps;
import xyz.ottr.lutra.bottr.model.Source;
import xyz.ottr.lutra.model.Argument;
import xyz.ottr.lutra.system.Message;
import xyz.ottr.lutra.system.Result;
import xyz.ottr.lutra.system.ResultStream;
public abstract class AbstractSPARQLSource implements Source {
protected abstract Result getQueryExecution(String query);
private final PrefixMapping prefixes;
protected AbstractSPARQLSource(PrefixMapping prefixes) {
this.prefixes = prefixes;
}
protected AbstractSPARQLSource() {
this(PrefixMapping.Factory.create());
}
protected Result getQuery(String queryString) {
// Add prefixes to an empty query to get syntax correct
Query prefixesOnly = QueryFactory.create();
prefixesOnly.setPrefixMapping(this.prefixes);
try {
return Result.of(QueryFactory.create(prefixesOnly.serialize() + queryString));
} catch (JenaException ex) {
return Result.error("Error creating SPARQL query: " + ex.getMessage());
}
}
public ResultStream> execute(String query) {
return streamQuery(query, Result::of);
}
public ResultStream> execute(String query, ArgumentMaps argumentMaps) {
return streamQuery(query, argumentMaps);
}
private void addQueryLimit(Query query) {
int globalLimit = BOTTR.Settings.getRDFSourceQueryLimit();
long currentLimit = query.getLimit();
if (globalLimit > 0 && globalLimit < currentLimit) {
query.setLimit(globalLimit);
}
}
private ResultStream streamQuery(String query, Function, Result> translationFunction) {
return getQueryExecution(query)
.mapToStream(exec -> {
Query q = exec.getQuery();
if (q.isSelectType()) {
addQueryLimit(q);
ResultSet resultSet = exec.execSelect();
return getResultSetStream(resultSet, translationFunction);
//} else if (q.isAskType()) {
// boolean system = exec.execAsk();
// return ResultStream.innerOf(system ? TRUE : FALSE);
} else {
return ResultStream.of(Result.empty(Message.error(
"Unsupported SPARQL query type. Query must be SELECT.")));
}
});
}
private ResultStream getResultSetStream(ResultSet resultSet, Function, Result> translationFunction) {
List columns = resultSet.getResultVars();
// TODO: does this work when a get returns null? will there be a hole in the list? Must test.
Function> rowCreator = (sol) ->
Result.of(
columns.stream()
.map(sol::get)
.collect(Collectors.toList()))
.flatMap(translationFunction);
return new ResultStream<>(StreamSupport.stream(
new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer super Result> action) {
if (!resultSet.hasNext()) {
return false;
} else {
action.accept(rowCreator.apply(resultSet.next()));
return true;
}
}
}, false));
}
@Override
public ArgumentMap createArgumentMap(PrefixMapping prefixMapping) {
return new RDFNodeArgumentMap(prefixMapping);
}
}