package eu.lod2.rsine.queryhandling;
import eu.lod2.rsine.changesetstore.ChangeSetStore;
import eu.lod2.rsine.queryhandling.policies.IEvaluationPolicy;
import eu.lod2.rsine.registrationservice.Condition;
import eu.lod2.rsine.registrationservice.NotificationQuery;
import org.openrdf.OpenRDFException;
import org.openrdf.query.*;
import org.openrdf.query.algebra.evaluation.QueryBindingSet;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@Component
public class QueryEvaluator {
private final Logger logger = LoggerFactory.getLogger(QueryEvaluator.class);
public final static String QUERY_LAST_ISSUED = "QUERY_LAST_ISSUED";
public final static String AUTH_URI = "AUTH_URI";
@Autowired
private ChangeSetStore changeSetStore;
@Autowired
private QueryProfiler queryProfiler;
@Autowired
private Repository managedStoreRepo;
private String authoritativeUri = "";
public QueryEvaluator() {}
public QueryEvaluator(String authoritativeUri) {
this.authoritativeUri = authoritativeUri;
}
public Collection<String> evaluate(NotificationQuery query, IEvaluationPolicy usePolicy) throws OpenRDFException
{
usePolicy.checkEvaluationNeeded(query);
String issuedQuery = fillInPlaceholders(query);
long start = System.currentTimeMillis();
RepositoryConnection repCon = managedStoreRepo.getConnection();
try {
Collection<String> messages = createMessages(query, issuedQuery, repCon);
queryProfiler.log(issuedQuery, System.currentTimeMillis() - start);
return messages;
}
finally {
repCon.close();
}
}
private String fillInPlaceholders(NotificationQuery query) {
String sparqlQuery;
sparqlQuery = amendChangeSetsTimeConstraint(query);
sparqlQuery = sparqlQuery.replace(AUTH_URI, authoritativeUri);
return sparqlQuery;
}
/**
* Replaces the placeholder in the subscriber query with the date the query has been last issued. This way only
* changesets that have an creation date after the query hast been last issued are returned
*/
private String amendChangeSetsTimeConstraint(NotificationQuery query) {
String sparqlQuery = query.getSparqlQuery();
String queryLastIssuedDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSZ").format(query.getLastIssued());
// fix timezone format
queryLastIssuedDate = new StringBuffer(queryLastIssuedDate).insert(queryLastIssuedDate.length() - 2, ":").toString();
return sparqlQuery.replace(QUERY_LAST_ISSUED, queryLastIssuedDate);
}
private Collection<String> createMessages(NotificationQuery query,
String issuedQuery,
RepositoryConnection managedStoreCon) throws OpenRDFException
{
Collection<BindingSet> results = changeSetStore.evaluateQuery(issuedQuery);
query.updateLastIssued();
Collection<String> messages = new HashSet<String>();
for (BindingSet bs : results) {
evaluateAuxiliary(query.getAuxiliaryQueries(), bs);
if (evaluateConditions(query.getConditions(), bs, managedStoreCon)) {
messages.add(query.getBindingSetFormatter().toMessage(bs));
}
}
return messages;
}
private boolean evaluateConditions(Iterator<Condition> conditions,
BindingSet bs,
RepositoryConnection managedStoreCon)
{
boolean allConditionsFulfilled = true;
while (conditions.hasNext()) {
Condition condition = conditions.next();
allConditionsFulfilled &= evaluateCondition(condition, bs, managedStoreCon);
}
return allConditionsFulfilled;
}
private boolean evaluateCondition(Condition condition, BindingSet bs, RepositoryConnection managedStoreCon)
{
try {
BooleanQuery booleanQuery = managedStoreCon.prepareBooleanQuery(QueryLanguage.SPARQL, condition.getAskQuery());
setBinding(booleanQuery, bs);
return booleanQuery.evaluate() == condition.getExpectedResult();
}
catch (Exception e) {
logger.error("Error evaluating condition. Query: '" +condition.getAskQuery()+ "'", e);
}
return false;
}
private void setBinding(Operation query, BindingSet bindingSet) {
for (String bindingName : bindingSet.getBindingNames()) {
query.setBinding(bindingName, bindingSet.getBinding(bindingName).getValue());
}
}
private void evaluateAuxiliary(Iterator<String> auxQueries, BindingSet bindingSet) throws OpenRDFException {
RepositoryConnection repCon = managedStoreRepo.getConnection();
try {
if (bindingSet instanceof QueryBindingSet) {
while (auxQueries.hasNext()) {
addBindings(auxQueries.next(), repCon, (QueryBindingSet) bindingSet);
}
}
else {
logger.error("Cannot extend binding set");
}
} catch (MalformedQueryException e) {
e.printStackTrace();
} finally {
repCon.close();
}
}
private void addBindings(String query,
RepositoryConnection repCon,
QueryBindingSet bindingSet)
throws OpenRDFException
{
TupleQuery tupleQuery = repCon.prepareTupleQuery(QueryLanguage.SPARQL, query);
setBinding(tupleQuery, bindingSet);
TupleQueryResult result = tupleQuery.evaluate();
while (result.hasNext()) {
BindingSet resultBindings = result.next();
bindingSet.addAll(resultBindings);
}
}
}