/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
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 Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.blueprints;
import info.aduna.iteration.CloseableIteration;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import org.apache.log4j.Logger;
import org.openrdf.OpenRDFException;
import org.openrdf.model.Literal;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.StatementImpl;
import org.openrdf.query.BindingSet;
import org.openrdf.query.BooleanQuery;
import org.openrdf.query.GraphQueryResult;
import org.openrdf.query.QueryInterruptedException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.query.Update;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import com.bigdata.blueprints.BigdataGraphAtom.EdgeAtom;
import com.bigdata.blueprints.BigdataGraphAtom.EdgeLabelAtom;
import com.bigdata.blueprints.BigdataGraphAtom.ElementType;
import com.bigdata.blueprints.BigdataGraphAtom.ExistenceAtom;
import com.bigdata.blueprints.BigdataGraphAtom.PropertyAtom;
import com.bigdata.blueprints.BigdataGraphEdit.Action;
import com.bigdata.rdf.internal.XSD;
import com.bigdata.rdf.internal.impl.extensions.DateTimeExtension;
import com.bigdata.rdf.sail.BigdataSailBooleanQuery;
import com.bigdata.rdf.sail.BigdataSailGraphQuery;
import com.bigdata.rdf.sail.BigdataSailRepositoryConnection;
import com.bigdata.rdf.sail.BigdataSailTupleQuery;
import com.bigdata.rdf.sail.QueryCancelledException;
import com.bigdata.rdf.sail.RDRHistory;
import com.bigdata.rdf.sail.model.RunningQuery;
import com.bigdata.rdf.sparql.ast.ASTContainer;
import com.bigdata.rdf.sparql.ast.QueryHints;
import com.bigdata.rdf.sparql.ast.QueryType;
import com.bigdata.rdf.store.AbstractTripleStore;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Features;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.GraphQuery;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.util.io.graphml.GraphMLReader;
import cutthecrap.utils.striterators.Filter;
import cutthecrap.utils.striterators.ICloseableIterator;
import cutthecrap.utils.striterators.IStriterator;
import cutthecrap.utils.striterators.Resolver;
import cutthecrap.utils.striterators.Striterator;
/**
* A base class for a Blueprints wrapper around a bigdata back-end.
*
* @author mikepersonick
*
*/
public abstract class BigdataGraph implements Graph {
private static final transient Logger log = Logger.getLogger(BigdataGraph.class);
private static final transient Logger sparqlLog = Logger.getLogger(
BigdataGraph.class.getName() + ".SparqlLogger");
/**
* Maximum number of chars to print through the SparqlLogger.
*/
public static final int SPARQL_LOG_MAX = 10000;
public interface Options {
/**
* Allow multiple edges with the same edge id. Useful for assigning
* by-reference properties (e.g. vertex type).
*/
String LAX_EDGES = BigdataGraph.class.getName() + ".laxEdges";
/**
* Use an append model for properties (rather than replace).
*/
String LAX_PROPERTIES = BigdataGraph.class.getName() + ".laxProperties";
/**
* Set a global query timeout to apply to issuing queries.
*/
String MAX_QUERY_TIME = BigdataGraph.class.getName() + ".maxQueryTime";
}
/**
* Max Query Time used to globally set the query timeout.
*
* Default is 0 (unlimited)
*/
protected final int maxQueryTime;
/**
* URI used for typing elements.
*/
protected final URI TYPE;
/**
* URI used to represent a Vertex.
*/
protected final URI VERTEX;
/**
* URI used to represent a Edge.
*/
protected final URI EDGE;
/**
* URI used for labeling edges.
*/
protected final URI LABEL;
/**
* Factory for round-tripping between Blueprints data and RDF data.
*/
protected final BlueprintsValueFactory factory;
/**
* Allow re-use of edge identifiers.
*/
private final boolean laxEdges;
/**
* If true, use pure append mode (don't check old property values).
*/
protected final boolean laxProperties;
public BigdataGraph(final BlueprintsValueFactory factory) {
this(factory, new Properties());
}
public BigdataGraph(final BlueprintsValueFactory factory,
final Properties props) {
this.factory = factory;
this.laxEdges = Boolean.valueOf(props.getProperty(
Options.LAX_EDGES, "false"));
this.laxProperties = Boolean.valueOf(props.getProperty(
Options.LAX_PROPERTIES, "false"));
this.maxQueryTime = Integer.parseInt(props.getProperty(
Options.MAX_QUERY_TIME, "0"));
this.TYPE = factory.getTypeURI();
this.VERTEX = factory.getVertexURI();
this.EDGE = factory.getEdgeURI();
this.LABEL = factory.getLabelURI();
}
/**
* For some reason this is part of the specification (i.e. part of the
* Blueprints test suite).
*/
public String toString() {
return getClass().getSimpleName().toLowerCase();
}
/**
* Return the factory used to round-trip between Blueprints values and
* RDF values.
*/
public BlueprintsValueFactory getValueFactory() {
return factory;
}
/**
* Different implementations will return different types of connections
* depending on the mode (client/server, embedded, read-only, etc.)
*/
public abstract RepositoryConnection cxn() throws Exception;
/**
* Return a single-valued property for an edge or vertex.
*
* @see {@link BigdataElement}
*/
public Object getProperty(final URI uri, final String prop) {
return getProperty(uri, factory.toPropertyURI(prop));
}
/**
* Return a single-valued property for an edge or vertex.
*
* @see {@link BigdataElement}
*/
public Object getProperty(final URI uri, final URI prop) {
try {
final RepositoryResult<Statement> result =
cxn().getStatements(uri, prop, null, false);
try {
if (result.hasNext()) {
final Statement stmt = result.next();
if (!result.hasNext()) {
/*
* Single value.
*/
return getProperty(stmt.getObject());
} else {
/*
* Multi-value, use a list.
*/
final List<Object> list = new LinkedList<Object>();
list.add(getProperty(stmt.getObject()));
while (result.hasNext()) {
list.add(getProperty(result.next().getObject()));
}
return list;
}
}
return null;
} finally {
result.close();
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected Object getProperty(final Value value) {
if (!(value instanceof Literal)) {
throw new RuntimeException("not a property: " + value);
}
final Literal lit = (Literal) value;
final Object o = factory.fromLiteral(lit);
return o;
}
// /**
// * Return a multi-valued property for an edge or vertex.
// *
// * TODO get rid of me
// *
// * @see {@link BigdataElement}
// */
// public List<Object> getProperties(final URI uri, final String prop) {
//
// return getProperties(uri, factory.toPropertyURI(prop));
//
// }
//
// /**
// * Return a multi-valued property for an edge or vertex.
// *
// * TODO get rid of me
// *
// * @see {@link BigdataElement}
// */
// public List<Object> getProperties(final URI uri, final URI prop) {
//
// try {
//
// final RepositoryResult<Statement> result =
// getWriteConnection().getStatements(uri, prop, null, false);
//
// final List<Object> props = new LinkedList<Object>();
//
// while (result.hasNext()) {
//
// final Value value = result.next().getObject();
//
// if (!(value instanceof Literal)) {
// throw new RuntimeException("not a property: " + value);
// }
//
// final Literal lit = (Literal) value;
//
// props.add(factory.fromLiteral(lit));
//
// }
//
// return props;
//
// } catch (RuntimeException e) {
// throw e;
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//
// }
/**
* Return the property names for an edge or vertex.
*
* @see {@link BigdataElement}
*/
public Set<String> getPropertyKeys(final URI uri) {
try {
final RepositoryResult<Statement> result =
cxn().getStatements(uri, null, null, false);
try {
final Set<String> properties = new LinkedHashSet<String>();
while (result.hasNext()) {
final Statement stmt = result.next();
if (!(stmt.getObject() instanceof Literal)) {
continue;
}
if (stmt.getPredicate().equals(LABEL)) {
continue;
}
final String p =
factory.fromURI(stmt.getPredicate());
properties.add(p);
}
return properties;
} finally {
result.close();
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Remove all values for a particular property on an edge or vertex.
*
* @see {@link BigdataElement}
*/
public Object removeProperty(final URI uri, final String prop) {
return removeProperty(uri, factory.toPropertyURI(prop));
}
/**
* Remove all values for a particular property on an edge or vertex.
*
* @see {@link BigdataElement}
*/
public Object removeProperty(final URI uri, final URI prop) {
try {
final Object oldVal = getProperty(uri, prop);
cxn().remove(uri, prop, null);
return oldVal;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Set a single-value property on an edge or vertex (remove the old
* value first).
*
* @see {@link BigdataElement}
*/
public void setProperty(final URI s, final String prop, final Object val) {
setProperty(s, factory.toPropertyURI(prop), toLiterals(val));
}
protected Collection<Literal> toLiterals(final Object val) {
final Collection<Literal> literals = new LinkedList<Literal>();
if (val instanceof Collection) {
@SuppressWarnings("unchecked")
final Collection<Object> vals = (Collection<Object>) val;
for (Object o : vals) {
literals.add(factory.toLiteral(o));
}
} else if (val.getClass().isArray()) {
final int len = Array.getLength(val);
for (int i = 0; i < len; i++) {
final Object o = Array.get(val, i);
literals.add(factory.toLiteral(o));
}
} else {
literals.add(factory.toLiteral(val));
}
return literals;
}
// /**
// * Set a single-value property on an edge or vertex (remove the old
// * value first).
// *
// * @see {@link BigdataElement}
// */
// public void setProperty(final URI uri, final URI prop, final Literal val) {
//
// try {
//
// final RepositoryConnection cxn = getWriteConnection();
//
// if (!laxProperties) {
//
// // remove the old value
// cxn.remove(uri, prop, null);
//
// }
//
// // add the new value
// cxn.add(uri, prop, val);
//
// } catch (RuntimeException e) {
// throw e;
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//
// }
/**
* Set a multi-value property on an edge or vertex (remove the old
* values first).
*
* @see {@link BigdataElement}
*/
public void setProperty(final URI uri, final URI prop,
final Collection<Literal> vals) {
try {
final RepositoryConnection cxn = cxn();
if (!laxProperties) {
// remove the old value
cxn.remove(uri, prop, null);
}
// add the new values
for (Literal val : vals) {
cxn.add(uri, prop, val);
}
/*
* Add a bnode representing the array object ["a", "b", "c", "a"].
*
* <s> <p> "a" .
* <s> <p> "b" .
* <s> <p> "c" .
* << <s> <p> "a" >> <order> "1"^^xsd:int .
* << <s> <p> "b" >> <order> "2"^^xsd:int .
* << <s> <p> "c" >> <order> "3"^^xsd:int .
* << <s> <p> "a" >> <order> "4"^^xsd:int .
*/
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// /**
// * Add a property on an edge or vertex (multi-value property extension).
// *
// * @see {@link BigdataElement}
// */
// public void addProperty(final URI uri, final String prop, final Object val) {
//
// setProperty(uri, factory.toPropertyURI(prop), factory.toLiteral(val));
//
// }
//
// /**
// * Add a property on an edge or vertex (multi-value property extension).
// *
// * @see {@link BigdataElement}
// */
// public void addProperty(final URI uri, final URI prop, final Literal val) {
//
// try {
//
// getWriteConnection().add(uri, prop, val);
//
// } catch (RuntimeException e) {
// throw e;
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//
// }
/**
* Post a GraphML file to the remote server. (Bulk-upload operation.)
*/
public void loadGraphML(final String file) throws Exception {
GraphMLReader.inputGraph(this, file);
}
/**
* Add an edge.
*/
@Override
public Edge addEdge(final Object key, final Vertex from, final Vertex to,
final String label) {
return addEdge(key, from, to, label, false);
}
/**
* Add an edge.
*/
public Edge addEdge(final Object key, final Vertex from, final Vertex to,
final boolean anonymous) {
return addEdge(key, from, to, null, anonymous);
}
/**
* Add an edge.
*/
public Edge addEdge(final Object key, final Vertex from, final Vertex to,
final String label, final boolean anonymous) {
if (log.isInfoEnabled())
log.info("("+key+", "+from+", "+to+", "+label+")");
/*
* Null edge labels allowed for anonymous edges (in laxEdges mode).
*/
if (label == null && !laxEdges) {
throw new IllegalArgumentException();
}
if (key != null && !laxEdges) {
final Edge edge = getEdge(key);
if (edge != null) {
if (!(edge.getVertex(Direction.OUT).equals(from) &&
(edge.getVertex(Direction.IN).equals(to)))) {
throw new IllegalArgumentException("edge already exists: " + key);
}
}
}
final String eid = key != null ? key.toString() : UUID.randomUUID().toString();
final URI edgeURI = factory.toEdgeURI(eid);
try {
// do we need to check this?
// if (cxn().hasStatement(edgeURI, TYPE, EDGE, false)) {
// throw new IllegalArgumentException("edge " + eid + " already exists");
// }
final URI fromURI = factory.toVertexURI(from.getId().toString());
final URI toURI = factory.toVertexURI(to.getId().toString());
final RepositoryConnection cxn = cxn();
cxn.add(fromURI, edgeURI, toURI);
if (label != null) {
cxn.add(edgeURI, LABEL, factory.toLiteral(label));
//2015-07-15: Please review this change for correctness. It is
//confirmed to resolve the projection test failure.
if (!anonymous) {
cxn.add(edgeURI, TYPE, EDGE);
}
}
return new BigdataEdge(new StatementImpl(fromURI, edgeURI, toURI), this);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Add a vertex.
*/
@Override
public Vertex addVertex(final Object key) {
if (log.isInfoEnabled())
log.info("("+key+")");
try {
final String vid = key != null ?
key.toString() : UUID.randomUUID().toString();
final URI uri = factory.toVertexURI(vid);
// do we need to check this?
// if (cxn().hasStatement(vertexURI, TYPE, VERTEX, false)) {
// throw new IllegalArgumentException("vertex " + vid + " already exists");
// }
cxn().add(uri, TYPE, VERTEX);
return new BigdataVertex(uri, this);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Lookup an edge.
*/
@Override
public Edge getEdge(final Object key) {
if (log.isInfoEnabled())
log.info("("+key+")");
if (key == null)
throw new IllegalArgumentException();
try {
final URI edge = factory.toEdgeURI(key.toString());
final RepositoryResult<Statement> result =
cxn().getStatements(null, edge, null, false);
try {
if (result.hasNext()) {
final Statement stmt = result.next();
if (result.hasNext()) {
throw new RuntimeException(
"duplicate edge: " + key);
}
return new BigdataEdge(stmt, this);
}
return null;
} finally {
result.close();
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Iterate all edges.
*/
@Override
public Iterable<Edge> getEdges() {
if (log.isInfoEnabled())
log.info("");
try {
final URI wild = null;
return getEdges(wild, wild);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Find edges based on the from and to vertices and the edge labels, all
* optional parameters (can be null). The edge labels can be null to include
* all labels.
* <p>
*
* @param from
* the from vertex (null for wildcard)
* @param to
* the to vertex (null for wildcard)
* @param labels
* the edge labels to consider (optional)
* @return the edges matching the supplied criteria
*/
Iterable<Edge> getEdges(final URI from, final URI to,
final String... labels) throws Exception {
final GraphQueryResult stmts = getElements(from, to, labels);
/*
* EdgeIterable will close the connection if necessary.
*/
return new EdgeIterable(stmts);
}
/**
* Translates the request to a high-performance SPARQL query:
*
* construct {
* ?from ?edge ?to .
* } where {
* ?edge rdf:type <Edge> .
*
* ?from ?edge ?to .
*
* # filter by edge label
* ?edge rdfs:label ?label .
* filter(?label in ("label1", "label2", ...)) .
* }
*/
protected GraphQueryResult getElements(final URI from, final URI to,
final String... labels) throws Exception {
final StringBuilder sb = new StringBuilder();
sb.append("construct { ?from ?edge ?to . } where {\n");
sb.append(" ?edge <"+TYPE+"> <"+EDGE+"> .\n");
sb.append(" ?from ?edge ?to .\n");
if (labels != null && labels.length > 0) {
if (labels.length == 1) {
sb.append(" ?edge <"+LABEL+"> \"").append(labels[0]).append("\" .\n");
} else {
sb.append(" ?edge <"+LABEL+"> ?label .\n");
sb.append(" filter(?label in (");
for (String label : labels) {
sb.append("\""+label+"\", ");
}
sb.setLength(sb.length()-2);
sb.append(")) .\n");
}
}
sb.append("}");
// bind the from and/or to
final String queryStr = sb.toString()
.replace("?from", from != null ? "<"+from+">" : "?from")
.replace("?to", to != null ? "<"+to+">" : "?to");
final org.openrdf.query.GraphQuery query =
cxn().prepareGraphQuery(QueryLanguage.SPARQL, queryStr);
final GraphQueryResult stmts = query.evaluate();
return stmts;
}
/**
* Find edges based on a SPARQL construct query. The query MUST construct
* edge statements:
* <p>
* construct { ?from ?edge ?to } where { ... }
*
* @see {@link BigdataGraphQuery}
*/
Iterable<Edge> getEdges(final String queryStr) throws Exception {
final org.openrdf.query.GraphQuery query =
cxn().prepareGraphQuery(QueryLanguage.SPARQL, queryStr);
final GraphQueryResult stmts = query.evaluate();
/*
* EdgeIterable will close the connection if necessary.
*/
return new EdgeIterable(stmts);
}
/**
* Find vertices based on the supplied from and to vertices and the edge
* labels. One or the other (from and to) must be null (wildcard), but not
* both. Use getEdges() for wildcards on both the from and to. The edge
* labels can be null to include all labels.
*
* @param from
* the from vertex (null for wildcard)
* @param to
* the to vertex (null for wildcard)
* @param labels
* the edge labels to consider (optional)
* @return
* the vertices matching the supplied criteria
*/
Iterable<Vertex> getVertices(final URI from, final URI to,
final String... labels) throws Exception {
if (from != null && to != null) {
throw new IllegalArgumentException();
}
if (from == null && to == null) {
throw new IllegalArgumentException();
}
final GraphQueryResult stmts = getElements(from, to, labels);
/*
* VertexIterable will close the connection if necessary.
*/
return new VertexIterable(stmts, from == null);
}
/**
* Find vertices based on a SPARQL construct query. If the subject parameter
* is true, the vertices will be taken from the subject position of the
* constructed statements, otherwise they will be taken from the object
* position.
*
* @see {@link BigdataGraphQuery}
*/
Iterable<Vertex> getVertices(final String queryStr, final boolean subject)
throws Exception {
final org.openrdf.query.GraphQuery query =
cxn().prepareGraphQuery(QueryLanguage.SPARQL, queryStr);
final GraphQueryResult stmts = query.evaluate();
/*
* VertexIterable will close the connection if necessary.
*/
return new VertexIterable(stmts, subject);
}
/**
* Find edges with the supplied property value.
*
* construct {
* ?from ?edge ?to .
* }
* where {
* ?edge <prop> <val> .
* ?from ?edge ?to .
* }
*/
@Override
public Iterable<Edge> getEdges(final String prop, final Object val) {
if (log.isInfoEnabled())
log.info("("+prop+", "+val+")");
final URI p = factory.toPropertyURI(prop);
final Literal o = factory.toLiteral(val);
try {
final StringBuilder sb = new StringBuilder();
sb.append("construct { ?from ?edge ?to . } where {\n");
sb.append(" ?edge <"+p+"> "+o+" .\n");
sb.append(" ?from ?edge ?to .\n");
sb.append("}");
final String queryStr = sb.toString();
return getEdges(queryStr);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Lookup a vertex.
*/
@Override
public Vertex getVertex(final Object key) {
if (log.isInfoEnabled())
log.info("("+key+")");
if (key == null)
throw new IllegalArgumentException();
final URI uri = factory.toVertexURI(key.toString());
try {
if (cxn().hasStatement(uri, TYPE, VERTEX, false)) {
return new BigdataVertex(uri, this);
}
return null;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Iterate all vertices.
*/
@Override
public Iterable<Vertex> getVertices() {
if (log.isInfoEnabled())
log.info("");
try {
final RepositoryResult<Statement> result =
cxn().getStatements(null, TYPE, VERTEX, false);
/*
* VertexIterable will close the connection if necessary.
*/
return new VertexIterable(result, true);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Find vertices with the supplied property value.
*/
@Override
public Iterable<Vertex> getVertices(final String prop, final Object val) {
if (log.isInfoEnabled())
log.info("("+prop+", "+val+")");
final URI p = factory.toPropertyURI(prop);
final Literal o = factory.toLiteral(val);
try {
final RepositoryResult<Statement> result =
cxn().getStatements(null, p, o, false);
/*
* VertexIterable will close the connection if necessary.
*/
return new VertexIterable(result, true);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Providing an override implementation for our GraphQuery to avoid the
* low-performance scan and filter paradigm. See {@link BigdataGraphQuery}.
*/
@Override
public GraphQuery query() {
if (log.isInfoEnabled())
log.info("");
// return new DefaultGraphQuery(this);
return new BigdataGraphQuery(this);
}
/**
* Remove an edge and its properties.
*/
@Override
public void removeEdge(final Edge edge) {
try {
final RepositoryConnection cxn = cxn();
final URI uri = factory.toURI(edge);
if (!cxn.hasStatement(uri, TYPE, EDGE, false)) {
throw new IllegalStateException();
}
final URI wild = null;
// remove the edge statement
cxn.remove(wild, uri, wild);
// remove its properties
cxn.remove(uri, wild, wild);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Remove a vertex and its edges and properties.
*
* TODO FIXME I am not fully removing dependent edges.
*/
@Override
public void removeVertex(final Vertex vertex) {
try {
final RepositoryConnection cxn = cxn();
final URI uri = factory.toURI(vertex);
if (!cxn.hasStatement(uri, TYPE, VERTEX, false)) {
throw new IllegalStateException();
}
final URI wild = null;
// remove outgoing edges and properties
cxn.remove(uri, wild, wild);
// remove incoming edges
cxn.remove(wild, wild, uri);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Translate a collection of Bigdata statements into an iteration of
* Blueprints vertices.
*
* @author mikepersonick
*
* TODO FIXME Find a better way to close the connection associated with
* this iterable.
*/
public class VertexIterable implements Iterable<Vertex>, Iterator<Vertex> {
private final CloseableIteration<Statement, ? extends OpenRDFException> stmts;
private final boolean subject;
private final List<Vertex> cache;
public VertexIterable(
final CloseableIteration<Statement, ? extends OpenRDFException> stmts,
final boolean subject) {
this.stmts = stmts;
this.subject = subject;
this.cache = new LinkedList<Vertex>();
}
@Override
public boolean hasNext() {
try {
return stmts.hasNext();
} catch (OpenRDFException e) {
throw new RuntimeException(e);
}
}
@Override
public Vertex next() {
try {
final Statement stmt = stmts.next();
final URI v = (URI)
(subject ? stmt.getSubject() : stmt.getObject());
final Vertex vertex = new BigdataVertex(v, BigdataGraph.this);
cache.add(vertex);
return vertex;
} catch (OpenRDFException e) {
throw new RuntimeException(e);
} finally {
if (!hasNext()) {
try {
stmts.close();
} catch (OpenRDFException e) {
log.warn("Could not close result");
}
}
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator<Vertex> iterator() {
return hasNext() ? this : cache.iterator();
}
}
/**
* Translate a collection of Bigdata statements into an iteration of
* Blueprints edges.
*
* @author mikepersonick
*
* TODO FIXME Find a better way to close the connection associated with
* this iterable.
*/
public class EdgeIterable implements Iterable<Edge>, Iterator<Edge> {
private final CloseableIteration<Statement, ? extends OpenRDFException> stmts;
private final List<Edge> cache;
public EdgeIterable(
final CloseableIteration<Statement, ? extends OpenRDFException> stmts) {
this.stmts = stmts;
this.cache = new LinkedList<Edge>();
}
@Override
public boolean hasNext() {
try {
return stmts.hasNext();
} catch (OpenRDFException e) {
throw new RuntimeException(e);
}
}
@Override
public Edge next() {
try {
final Statement stmt = stmts.next();
final Edge edge = new BigdataEdge(stmt, BigdataGraph.this);
cache.add(edge);
return edge;
} catch (OpenRDFException e) {
throw new RuntimeException(e);
} finally {
if (!hasNext()) {
try {
stmts.close();
} catch (OpenRDFException e) {
log.warn("Could not close result");
}
}
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator<Edge> iterator() {
return hasNext() ? this : cache.iterator();
}
}
/**
* Fuse two iterables together into one. Useful for combining IN and OUT
* edges for a vertex.
*/
public final <T> Iterable<T> fuse(final Iterable<T>... args) {
return new FusedIterable<T>(args);
}
/**
* Fuse two iterables together into one. Useful for combining IN and OUT
* edges for a vertex.
*
* @author mikepersonick
*/
public class FusedIterable<T> implements Iterable<T>, Iterator<T> {
private final Iterable<T>[] args;
private transient int i = 0;
private transient Iterator<T> curr;
public FusedIterable(final Iterable<T>... args) {
this.args = args;
this.curr = args[0].iterator();
}
@Override
public boolean hasNext() {
if (curr.hasNext()) {
return true;
}
while (!curr.hasNext() && i < (args.length-1)) {
curr = args[++i].iterator();
if (curr.hasNext()) {
return true;
}
}
return false;
}
@Override
public T next() {
return curr.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public Iterator<T> iterator() {
return this;
}
}
/**
* Project a subgraph using a SPARQL query.
* <p>
* Warning: You MUST close this iterator when finished.
*/
public ICloseableIterator<BigdataGraphAtom> project(final String queryStr)
throws Exception {
return this.project(queryStr,UUID.randomUUID().toString());
}
/**
* Project a subgraph using a SPARQL query.
*
* This version allows passing an external system ID to allow association
* between queries in the query engine when using an Embedded Client.
*
* <p>
* Warning: You MUST close this iterator when finished.
*/
@SuppressWarnings("unchecked")
public ICloseableIterator<BigdataGraphAtom> project(final String queryStr,
String externalQueryId) throws Exception {
final RepositoryConnection cxn = cxn();
if (sparqlLog.isTraceEnabled()) {
sparqlLog.trace("query:\n"+ (queryStr.length() <= SPARQL_LOG_MAX
? queryStr : queryStr.substring(0, SPARQL_LOG_MAX)+" ..."));
}
final GraphQueryResult result;
UUID queryId = null;
try {
final org.openrdf.query.GraphQuery query =
cxn.prepareGraphQuery(QueryLanguage.SPARQL, queryStr);
setMaxQueryTime(query);
if (query instanceof BigdataSailGraphQuery
&& cxn instanceof BigdataSailRepositoryConnection) {
final BigdataSailGraphQuery bdtq = (BigdataSailGraphQuery) query;
queryId = setupQuery((BigdataSailRepositoryConnection) cxn,
bdtq.getASTContainer(), QueryType.CONSTRUCT,
externalQueryId);
}
if (sparqlLog.isTraceEnabled()) {
if (query instanceof BigdataSailGraphQuery) {
final BigdataSailGraphQuery bdgq = (BigdataSailGraphQuery) query;
sparqlLog.trace("optimized AST:\n"+bdgq.optimize());
}
}
result = query.evaluate();
} catch (Exception ex) {
if (queryId != null) {
/*
* In case the exception happens during evaluate().
*/
finalizeQuery(queryId);
}
throw ex;
}
final IStriterator sitr = new Striterator(new WrappedResult<Statement>(
result, queryId));
sitr.addFilter(new Filter() {
private static final long serialVersionUID = 1L;
@Override
public boolean isValid(final Object e) {
final Statement stmt = (Statement) e;
// do not project history
return stmt.getSubject() instanceof URI;
}
});
sitr.addFilter(new Resolver() {
private static final long serialVersionUID = 1L;
@Override
protected Object resolve(final Object e) {
final Statement stmt = (Statement) e;
return toGraphAtom(stmt);
}
});
return (ICloseableIterator<BigdataGraphAtom>) sitr;
}
/**
* Convert a unit of RDF data to an atomic unit of PG data.
*/
protected BigdataGraphAtom toGraphAtom(final Statement stmt) {
final URI s = (URI) stmt.getSubject();
final URI p = (URI) stmt.getPredicate();
final Value o = stmt.getObject();
return toGraphAtom(s, p, o);
}
/**
* Convert a unit of RDF data to an atomic unit of PG data.
*/
protected BigdataGraphAtom toGraphAtom(final URI s, final URI p, final Value o) {
final String sid = factory.fromURI(s);
final String pid = factory.fromURI(p);
final BigdataGraphAtom atom;
if (o instanceof URI) {
/*
* Either an edge or an element type statement.
*/
if (p.equals(factory.getTypeURI()) &&
(o.equals(factory.getVertexURI()) || o.equals(factory.getEdgeURI()))) {
/*
* Element type.
*/
if (o.equals(factory.getVertexURI())) {
atom = new ExistenceAtom(sid, ElementType.VERTEX);
} else {
atom = new ExistenceAtom(sid, ElementType.EDGE);
}
} else {
/*
* Edge.
*/
final String oid = factory.fromURI((URI) o);
atom = new EdgeAtom(pid, sid, oid);
}
} else {
/*
* A property or the edge label.
*/
if (p.equals(factory.getLabelURI())) {
/*
* Edge label.
*/
final String label = factory.fromLiteral((Literal) o).toString();
atom = new EdgeLabelAtom(sid, label);
} else {
/*
* Property.
*/
final Object oval = factory.fromLiteral((Literal) o);
atom = new PropertyAtom(sid, pid, oval);
}
}
return atom;
}
/**
* Select results using a SPARQL query.
* <p>
* Warning: You MUST close this iterator when finished.
*/
@SuppressWarnings("unchecked")
public ICloseableIterator<BigdataBindingSet> select(final String queryStr)
throws Exception {
return this.select(queryStr, UUID.randomUUID().toString());
}
/**
* Select results using a SPARQL query.
* <p>
* Warning: You MUST close this iterator when finished.
*/
@SuppressWarnings("unchecked")
public ICloseableIterator<BigdataBindingSet> select(final String queryStr,
String externalQueryId) throws Exception {
final RepositoryConnection cxn = cxn();
if (sparqlLog.isTraceEnabled()) {
sparqlLog.trace("query:\n"+ (queryStr.length() <= SPARQL_LOG_MAX
? queryStr : queryStr.substring(0, SPARQL_LOG_MAX)+" ..."));
}
final TupleQueryResult result;
UUID queryId = null;
try {
final TupleQuery query = (TupleQuery)
cxn.prepareTupleQuery(QueryLanguage.SPARQL, queryStr);
setMaxQueryTime(query);
if (query instanceof BigdataSailTupleQuery
&& cxn instanceof BigdataSailRepositoryConnection) {
final BigdataSailTupleQuery bdtq = (BigdataSailTupleQuery) query;
queryId = setupQuery((BigdataSailRepositoryConnection) cxn,
bdtq.getASTContainer(), QueryType.SELECT,
externalQueryId);
}
if (sparqlLog.isTraceEnabled()) {
if (query instanceof BigdataSailTupleQuery) {
final BigdataSailTupleQuery bdtq = (BigdataSailTupleQuery) query;
sparqlLog.trace("optimized AST:\n"+bdtq.optimize());
}
}
result = query.evaluate();
} catch (Exception ex) {
if (queryId != null) {
/*
* In case the exception happens during evaluate().
*/
finalizeQuery(queryId);
}
throw ex;
}
final IStriterator sitr = new Striterator(
new WrappedResult<BindingSet>(result, queryId));
sitr.addFilter(new Resolver() {
private static final long serialVersionUID = 1L;
@Override
protected Object resolve(final Object e) {
final BindingSet bs = (BindingSet) e;
return convert(bs);
}
});
return (ICloseableIterator<BigdataBindingSet>) sitr;
}
/**
* Convert SPARQL/RDF results into PG form.
*/
protected BigdataBindingSet convert(final BindingSet bs) {
final BigdataBindingSet bbs = new BigdataBindingSet();
for (String key : bs.getBindingNames()) {
final Value val= bs.getBinding(key).getValue();
final Object o;
if (val instanceof Literal) {
o = factory.fromLiteral((Literal) val);
} else if (val instanceof URI) {
o = factory.fromURI((URI) val);
} else {
continue;
}
bbs.put(key, o);
}
return bbs;
}
/**
* Select results using a SPARQL query.
*/
public boolean ask(final String queryStr) throws Exception {
return ask(queryStr, UUID.randomUUID().toString());
}
/**
* Select results using a SPARQL query.
*/
public boolean ask(final String queryStr, String externalQueryId)
throws Exception {
final RepositoryConnection cxn = cxn();
UUID queryId = null;
try {
final BooleanQuery query = (BooleanQuery)
cxn.prepareBooleanQuery(QueryLanguage.SPARQL, queryStr);
setMaxQueryTime(query);
if (query instanceof BigdataSailBooleanQuery
&& cxn instanceof BigdataSailRepositoryConnection) {
final BigdataSailBooleanQuery bdtq = (BigdataSailBooleanQuery) query;
queryId = setupQuery((BigdataSailRepositoryConnection) cxn,
bdtq.getASTContainer(), QueryType.ASK,
externalQueryId);
}
final boolean result = query.evaluate();
// finalizeQuery(queryId);
return result;
} finally {
if (queryId != null) {
/*
* In case the exception happens during evaluate().
*/
finalizeQuery(queryId);
}
}
}
/**
* Update graph using SPARQL Update.
*/
public void update(final String queryStr) throws Exception {
final String randomUUID = UUID.randomUUID().toString();
update(queryStr, randomUUID);
}
/**
* Update graph using SPARQL Update.
*/
public void update(final String queryStr, final String extQueryId)
throws Exception {
try {
final Update update =
cxn().prepareUpdate(QueryLanguage.SPARQL, queryStr);
update.execute();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Sparql template for history query.
*/
private static final String HISTORY_TEMPLATE =
"prefix hint: <"+QueryHints.NAMESPACE+">\n" +
"select ?s ?p ?o ?action ?time\n" +
"where {\n"+
" bind(<< ?s ?p ?o >> as ?sid) . \n" +
" hint:Prior hint:history true . \n" +
" ?sid ?action ?time . \n" +
"}";
/**
* If history is enabled, return an iterator of historical graph edits
* related to any of the supplied ids. To enable history, make sure
* the database is in statement identifiers mode and that the RDR History
* class is enabled.
* <p>
* Warning: You MUST close this iterator when finished.
*
* @see {@link AbstractTripleStore.Options#STATEMENT_IDENTIFIERS}
* @see {@link AbstractTripleStore.Options#RDR_HISTORY_CLASS}
* @see {@link RDRHistory}
*/
public ICloseableIterator<BigdataGraphEdit> history(final List<URI> ids)
throws Exception {
final String randomUUID = UUID.randomUUID().toString();
return history(ids, randomUUID);
}
@SuppressWarnings("unchecked")
public ICloseableIterator<BigdataGraphEdit> history(final List<URI> ids,
final String extQueryId) throws Exception {
final RepositoryConnection cxn = cxn();
final StringBuilder sb = new StringBuilder(HISTORY_TEMPLATE);
if (ids.size() > 0) {
final StringBuilder vc = new StringBuilder();
vc.append(" values (?s) { \n");
for (URI id : ids) {
vc.append(" (<"+id+">) \n");
}
vc.append(" } \n");
sb.insert(sb.length()-1, vc.toString());
}
final String queryStr = sb.toString();
if (sparqlLog.isTraceEnabled()) {
sparqlLog.trace("query:\n"+ (queryStr.length() <= SPARQL_LOG_MAX
? queryStr : queryStr.substring(0, SPARQL_LOG_MAX)+" ..."));
}
final TupleQueryResult result;
UUID queryId = null;
try {
final TupleQuery query = (TupleQuery)
cxn.prepareTupleQuery(QueryLanguage.SPARQL, queryStr);
if (query instanceof BigdataSailTupleQuery
&& cxn instanceof BigdataSailRepositoryConnection) {
final BigdataSailTupleQuery bdtq = (BigdataSailTupleQuery) query;
queryId = setupQuery((BigdataSailRepositoryConnection) cxn,
bdtq.getASTContainer(), QueryType.SELECT,
extQueryId);
}
if (sparqlLog.isTraceEnabled()) {
if (query instanceof BigdataSailTupleQuery) {
final BigdataSailTupleQuery bdtq = (BigdataSailTupleQuery) query;
sparqlLog.trace("optimized AST:\n"+bdtq.optimize());
}
}
result = query.evaluate();
} catch (Exception ex) {
if (queryId != null) {
/*
* In case the exception happens during evaluate().
*/
finalizeQuery(queryId);
}
throw ex;
}
final IStriterator sitr = new Striterator(new WrappedResult<BindingSet>(
result, queryId
));
sitr.addFilter(new Resolver() {
private static final long serialVersionUID = 1L;
@Override
protected Object resolve(final Object e) {
final BindingSet bs = (BindingSet) e;
final URI s = (URI) bs.getValue("s");
final URI p = (URI) bs.getValue("p");
final Value o = bs.getValue("o");
final URI a = (URI) bs.getValue("action");
final Literal t = (Literal) bs.getValue("time");
if (!t.getDatatype().equals(XSD.DATETIME)) {
throw new RuntimeException("Unexpected timestamp in result: " + bs);
}
final BigdataGraphEdit.Action action;
if (a.equals(RDRHistory.Vocab.ADDED)) {
action = Action.Add;
} else if (a.equals(RDRHistory.Vocab.REMOVED)) {
action = Action.Remove;
} else {
throw new RuntimeException("Unexpected action in result: " + bs);
}
final BigdataGraphAtom atom = toGraphAtom(s, p, o);
final long timestamp = DateTimeExtension.getTimestamp(t.getLabel());
return new BigdataGraphEdit(action, atom, timestamp);
}
});
return (ICloseableIterator<BigdataGraphEdit>) sitr;
}
protected static final Features FEATURES = new Features();
@Override
public Features getFeatures() {
return FEATURES;
}
static {
FEATURES.supportsSerializableObjectProperty = false;
FEATURES.supportsBooleanProperty = true;
FEATURES.supportsDoubleProperty = true;
FEATURES.supportsFloatProperty = true;
FEATURES.supportsIntegerProperty = true;
FEATURES.supportsPrimitiveArrayProperty = true;
FEATURES.supportsUniformListProperty = true;
FEATURES.supportsMixedListProperty = true;
FEATURES.supportsLongProperty = true;
FEATURES.supportsMapProperty = false;
FEATURES.supportsStringProperty = true;
FEATURES.supportsDuplicateEdges = true;
FEATURES.supportsSelfLoops = true;
FEATURES.isPersistent = true;
FEATURES.isWrapper = false;
FEATURES.supportsVertexIteration = true;
FEATURES.supportsEdgeIteration = true;
FEATURES.supportsVertexIndex = false;
FEATURES.supportsEdgeIndex = false;
FEATURES.ignoresSuppliedIds = false;
FEATURES.supportsTransactions = false;
FEATURES.supportsIndices = true;
FEATURES.supportsKeyIndices = true;
FEATURES.supportsVertexKeyIndex = true;
FEATURES.supportsEdgeKeyIndex = true;
FEATURES.supportsEdgeRetrieval = true;
FEATURES.supportsVertexProperties = true;
FEATURES.supportsEdgeProperties = true;
FEATURES.supportsThreadedTransactions = false;
}
// /**
// * You MUST close this iterator when finished with it.
// */
// public static interface CloseableIterator<T> extends Iterator<T> {
//
// /**
// * Release any resources associated with this iterator.
// */
// void close();
//
// }
public class WrappedResult<E> implements ICloseableIterator<E> {
private final CloseableIteration<E,?> it;
private final UUID queryId;
public WrappedResult(final CloseableIteration<E,?> it) {
this.it = it;
this.queryId = null;
}
/**
* Allows you to pass a query UUID to perform a tear down
* when it exits.
*
* @param it
* @param queryId
*/
public WrappedResult(final CloseableIteration<E,?> it, UUID queryId) {
this.it = it;
this.queryId = queryId;
}
@Override
public boolean hasNext() {
try {
return it.hasNext();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public E next() {
try {
return (E) it.next();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
try {
finalizeQuery(queryId);
it.close();
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
// @Override
// protected void finalize() throws Throwable {
// super.finalize();
// System.err.println("closed: " + closed);
// }
}
/**
* Utility function to set the Query timeout to the global
* setting if it is configured.
*/
protected void setMaxQueryTime(final org.openrdf.query.Query query) {
if (maxQueryTime > 0) {
query.setMaxQueryTime(maxQueryTime);
}
}
/**
* Return a Collection of running queries
*
* @return
*/
public abstract Collection<RunningQuery> getRunningQueries();
/**
* Kill a running query specified by the UUID. Do nothing if the query has
* completed.
*
* @param queryId
*/
public abstract void cancel(UUID queryId);
/**
* Kill a running query specified by the UUID String.
* Do nothing if the query has completed.
*
* @param String uuid
*/
public abstract void cancel(String uuid);
/**
* Kill a running query specified by the RunningQuery object. Do nothing if
* the query has completed.
*
* @param r
*/
public abstract void cancel(RunningQuery r);
/**
* Return the {@link RunningQuery} for a currently executing SPARQL QUERY or
* UPDATE request.
*
* @param queryId2
* The {@link UUID} for the request.
*
* @return The {@link RunningQuery} iff it was found.
*/
public abstract RunningQuery getQueryById(final UUID queryId2);
/**
* Return the {@link RunningQuery} for a currently executing SPARQL QUERY or
* UPDATE request.
*
* @param queryId2
* The {@link UUID} for the request.
*
* @return The {@link RunningQuery} iff it was found.
*/
public abstract RunningQuery getQueryByExternalId(final String extQueryId);
/**
* Embedded clients can override this to access query management
* capabilities.
*
* @param cxn
* @param astContainer
*
* @return
*/
protected abstract UUID setupQuery(
final BigdataSailRepositoryConnection cxn,
ASTContainer astContainer, QueryType queryType, String extQueryId);
/**
* Wrapper method to clean up query and throw exception is interrupted.
*
* @param queryId
* @throws QueryCancelledException
*/
protected void finalizeQuery(final UUID queryId)
throws QueryCancelledException {
//Need to call before tearDown
final boolean isQueryCancelled = isQueryCancelled(queryId);
tearDownQuery(queryId);
if(isQueryCancelled){
if(log.isDebugEnabled()) {
log.debug(queryId + " execution canceled.");
}
throw new QueryCancelledException(queryId + " execution canceled.",
queryId);
}
}
/**
* Embedded clients can override this to access query management
* capabilities.
*
* @param absQuery
*/
protected abstract void tearDownQuery(UUID queryId);
/**
* Helper method to determine if a query was cancelled.
*
* @param queryId
* @return
*/
protected abstract boolean isQueryCancelled(final UUID queryId);
/**
* Is this a read-only view of the graph?
*/
public abstract boolean isReadOnly();
}