/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.atomojo.app.db.sparql;
import java.net.URI;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.atomojo.app.Nil;
import org.atomojo.app.db.DB;
import org.atomojo.app.db.Entry;
import org.atomojo.app.db.Feed;
import org.atomojo.app.db.Term;
import org.atomojo.app.db.TermInstance;
import org.atomojo.sparql.Expression;
import org.atomojo.sparql.LiteralRef;
import org.atomojo.sparql.NilRef;
import org.atomojo.sparql.Query;
import org.atomojo.sparql.Ref;
import org.atomojo.sparql.RelationalExpression;
import org.atomojo.sparql.SelectQuery;
import org.atomojo.sparql.TermRef;
import org.atomojo.sparql.Triple;
import org.atomojo.sparql.VariableRef;
/**
*
* @author alex
*/
public class QueryContext {
static final URI FEED_TERM = URI.create("http://www.atomojo.org/O/type/feed");
static final URI ENTRY_TERM = URI.create("http://www.atomojo.org/O/type/entry");
public interface ResultListener {
void onStart()
throws QueryException;
void onEnd()
throws QueryException;
void onEntry(Entry entry)
throws QueryException;
void onFeed(Feed feed)
throws QueryException;
}
static class Value {
Term term;
Object value;
Value(Term term,Object value)
{
this.term = term;
this.value = value;
}
}
Logger log;
DB db;
Query query;
boolean entryPivot;
boolean feedPivot;
boolean unsatisfiable;
String var;
Triple pivot;
URI pivotTerm;
Object pivotValue;
public QueryContext(Logger log,DB db,Query query)
throws QueryException
{
this.log = log;
this.db = db;
this.query = query;
if (!(query instanceof SelectQuery)) {
throw new QueryException("Only select queries are supported.");
}
SelectQuery select = (SelectQuery)query;
Set<String> returnVars = select.getReturnVariables();
if (returnVars.size()!=1) {
throw new QueryException("Only one entry/feed return variable is currently supported.");
}
var = returnVars.iterator().next();
List<Triple> triples = select.getWhereClause().getClauses();
pivot = null;
entryPivot = false;
feedPivot = false;
unsatisfiable = false;
Triple lastBind = null;
for (Triple t : triples) {
Ref subject = t.getSubject();
Ref predicate = t.getPredicate();
Ref object = t.getObject();
if (subject instanceof VariableRef && predicate instanceof TermRef && object instanceof NilRef) {
String name = ((VariableRef)subject).getName();
if (name.equals(var)) {
URI term = ((TermRef)predicate).getTerm();
if (term.equals(ENTRY_TERM)) {
entryPivot = true;
} else if (term.equals(FEED_TERM)) {
feedPivot = true;
} else if (pivot==null) {
pivot = t;
pivotTerm = term;
log.info("Pivoting on "+pivot);
pivotValue = pivot.getObject() instanceof LiteralRef ? ((LiteralRef)pivot.getObject()).getValue() : Nil.getInstance();
}
} else {
unsatisfiable = true;
}
} else if (subject instanceof TermRef) {
// should be ok (e.g. find a specific entry/feed)
log.info("Unsatisfiable triple "+t);
unsatisfiable = true;
} else if (subject instanceof VariableRef && predicate instanceof TermRef && object instanceof LiteralRef) {
// ok
// limit to var now
String name = ((VariableRef)subject).getName();
if (!name.equals(var)) {
log.info("Unsatisfiable triple "+t);
unsatisfiable = true;
} else if (pivot==null) {
pivot = t;
pivotTerm = ((TermRef)predicate).getTerm();
log.info("Pivoting on "+pivot);
pivotValue = pivot.getObject() instanceof LiteralRef ? ((LiteralRef)pivot.getObject()).getValue() : Nil.getInstance();
}
} else if (subject instanceof VariableRef && predicate instanceof TermRef && object instanceof VariableRef) {
// ok
// limit to var now
String name = ((VariableRef)subject).getName();
if (!name.equals(var)) {
log.info("Unsatisfiable triple "+t);
unsatisfiable = true;
} else {
lastBind = t;
}
} else {
// unsatisfiable
log.info("Unsatisfiable triple "+t);
unsatisfiable = true;
}
}
if (entryPivot && feedPivot) {
log.info("Entry and feed predicates on same variable is unsatisfiable.");
unsatisfiable = true;
}
if (!unsatisfiable && pivot==null) {
pivot = lastBind;
pivotTerm = ((TermRef)lastBind.getPredicate()).getTerm();
log.info("Pivoting on "+pivot);
pivotValue = pivot.getObject() instanceof LiteralRef ? ((LiteralRef)pivot.getObject()).getValue() : Nil.getInstance();
}
}
public void execute(ResultListener listener)
throws QueryException
{
if (unsatisfiable) {
listener.onStart();
listener.onEnd();
return;
}
try {
List<Triple> triples = query.getWhereClause().getClauses();
if (triples.size()==0) {
listener.onStart();
Iterator<Feed> feeds = db.getFeeds();
while (feeds.hasNext()) {
listener.onFeed(feeds.next());
}
Iterator<Entry> entries = db.getEntries();
while (entries.hasNext()) {
listener.onEntry(entries.next());
}
listener.onEnd();
} else if (entryPivot && triples.size()==1) {
listener.onStart();
Iterator<Entry> entries = db.getEntries();
while (entries.hasNext()) {
listener.onEntry(entries.next());
}
listener.onEnd();
} else if (feedPivot && triples.size()==1) {
listener.onStart();
Iterator<Feed> feeds = db.getFeeds();
while (feeds.hasNext()) {
listener.onFeed(feeds.next());
}
listener.onEnd();
} else {
if (!entryPivot && !feedPivot) {
// We'll query both feeds and entries
entryPivot = true;
feedPivot = true;
}
boolean skip = false;
Map<String,Term> bindings = new TreeMap<String,Term>();
List<Value> comparisons = new ArrayList<Value>();
for (Triple triple : query.getWhereClause().getClauses()) {
if (triple==pivot) {
continue;
}
URI uri = ((TermRef)triple.getPredicate()).getTerm();
Term term = db.findTerm(uri);
Ref object = triple.getObject();
if (object instanceof VariableRef) {
if (term!=null) {
bindings.put(((VariableRef)object).getName(),term);
}
} else if (object instanceof LiteralRef) {
if (term==null) {
skip = true;
} else {
comparisons.add(new Value(term,((LiteralRef)object).getValue()));
}
} else if (object instanceof NilRef) {
if (term==null) {
skip = true;
} else {
comparisons.add(new Value(term,Nil.getInstance()));
}
}
}
listener.onStart();
if (!skip) {
Term term = db.findTerm(pivotTerm);
if (term!=null) {
if (feedPivot) {
Iterator<TermInstance<Feed>> feeds = db.getFeedsByTerm(term,pivotValue);
while (feeds.hasNext()) {
TermInstance<Feed> termValue = feeds.next();
// need to compare if nil
if (pivotValue==Nil.getInstance() && termValue.getValue()!=Nil.getInstance()) {
continue;
}
Feed feed = termValue.getTarget();
Map<String,Object> variables = new TreeMap<String,Object>();
boolean ok = true;
// match values
for (Triple triple : query.getWhereClause().getClauses()) {
if (triple==pivot) {
continue;
}
for (Value value : comparisons) {
TermInstance<Feed> boundValue = feed.getTerm(value.term);
if (boundValue==null) {
ok = false;
break;
} else if (value.value==null && boundValue.getValue()!=null) {
ok = false;
break;
} else if (value.value!=null && !value.value.equals(boundValue.getValue())) {
ok = false;
break;
}
}
if (!ok) {
break;
}
for (String name : bindings.keySet()) {
Term t = bindings.get(name);
TermInstance<Feed> boundValue = feed.getTerm(t);
if (boundValue!=null && boundValue.getValue()!=null) {
variables.put(name,boundValue.getValue());
}
}
}
// do filter
ok = ok ? filter(variables,query.getWhereClause().getFilterExpressions()) : false;
if (ok) {
listener.onFeed(feed);
}
}
}
if (entryPivot) {
// TODO: code is almost the same as feed
Iterator<TermInstance<Entry>> entries = db.getEntriesByTerm(term,pivotValue);
while (entries.hasNext()) {
TermInstance<Entry> termValue = entries.next();
// need to compare if nil
if (pivotValue==Nil.getInstance() && termValue.getValue()!=Nil.getInstance()) {
continue;
}
Entry entry = termValue.getTarget();
Map<String,Object> variables = new TreeMap<String,Object>();
boolean ok = true;
// match values
for (Triple triple : query.getWhereClause().getClauses()) {
if (triple==pivot) {
continue;
}
for (Value value : comparisons) {
TermInstance<Entry> boundValue = entry.getTerm(value.term);
if (boundValue==null) {
ok = false;
break;
} else if (value.value==null && boundValue.getValue()!=null) {
ok = false;
break;
} else if (value.value!=null && !value.value.equals(boundValue.getValue())) {
ok = false;
break;
}
}
if (!ok) {
break;
}
for (String name : bindings.keySet()) {
Term t = bindings.get(name);
TermInstance<Entry> boundValue = entry.getTerm(t);
if (boundValue!=null && boundValue.getValue()!=null) {
variables.put(name,boundValue.getValue());
}
}
}
// do filter
ok = ok ? filter(variables,query.getWhereClause().getFilterExpressions()) : false;
if (ok) {
listener.onEntry(entry);
}
}
}
} else {
log.info("Could not find pivot term "+pivotTerm);
}
}
listener.onEnd();
}
} catch (SQLException ex) {
throw new QueryException("DB exception while running query.",ex);
}
}
boolean filter(Map<String,Object> variables,List<Expression> expressions)
{
boolean ok = true;
for (Expression expr : expressions) {
if (expr instanceof RelationalExpression) {
RelationalExpression r = (RelationalExpression)expr;
Object leftSide = null;
Object rightSide = null;
if (r.getLeftSide() instanceof VariableRef) {
leftSide = variables.get(((VariableRef)r.getLeftSide()).getName());
} else {
leftSide = ((LiteralRef)r.getLeftSide()).getValue();
}
if (r.getRightSide() instanceof VariableRef) {
rightSide = variables.get(((VariableRef)r.getRightSide()).getName());
} else {
rightSide = ((LiteralRef)r.getRightSide()).getValue();
}
if (rightSide==null || leftSide==null) {
ok = false;
break;
} else {
switch (r.getOperator()) {
case EQUALS:
ok = leftSide.equals(rightSide);
break;
case NOT_EQUALS:
ok = !leftSide.equals(rightSide);
break;
}
}
} else {
ok = false;
break;
}
}
return ok;
}
}