eu.fbk.knowledgestore.triplestore.virtuoso.VirtuosoTripleTransaction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ks-server-virtuoso Show documentation
Show all versions of ks-server-virtuoso Show documentation
The Virtuoso server module (ks-server-virtuoso) provides an
implementation of the Triple Store server-side component (API in
ks-triplestore) on top of the OpenLink Virtuoso triple store, a scalable
native code triple store that is accessed as an external service. The
Virtuoso Sesame driver, which builds on the Virtuoso JDBC driver, is
used to remotely access the Virtuoso server for writing data and
performing SPARQL queries. For performance reasons, data modification is
performed in a non-transactional way, with the module managing possible
failures in a way that trigger the external repopulation of the Virtuoso
triple store starting from the master copy of data stored in the Data
Store component.
package eu.fbk.knowledgestore.triplestore.virtuoso;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterators;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.ContextStatementImpl;
import org.openrdf.query.Binding;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.repository.RepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.CloseableIteratorIteration;
import info.aduna.iteration.EmptyIteration;
import info.aduna.iteration.IterationWrapper;
import virtuoso.sesame2.driver.VirtuosoRepositoryConnection;
import eu.fbk.knowledgestore.data.Data;
import eu.fbk.knowledgestore.data.Handler;
import eu.fbk.knowledgestore.runtime.DataCorruptedException;
import eu.fbk.knowledgestore.triplestore.SelectQuery;
import eu.fbk.knowledgestore.triplestore.TripleTransaction;
final class VirtuosoTripleTransaction implements TripleTransaction {
private static final Logger LOGGER = LoggerFactory.getLogger(VirtuosoTripleTransaction.class);
private final VirtuosoTripleStore store;
private final VirtuosoRepositoryConnection connection;
private final boolean readOnly;
private final long ts;
VirtuosoTripleTransaction(final VirtuosoTripleStore store, final boolean readOnly)
throws IOException {
assert store != null;
// try to connect to Virtuoso - under the hoods, a (pooled) JDBC connection is obtained
final long ts = System.currentTimeMillis();
final VirtuosoRepositoryConnection connection;
try {
connection = (VirtuosoRepositoryConnection) store.getVirtuoso().getConnection();
} catch (final RepositoryException ex) {
throw new IOException("Could not connect to Virtuoso", ex);
}
this.store = store;
this.connection = connection;
this.readOnly = readOnly;
this.ts = ts;
try {
// Following lines (that cause setAutoCommit(false) on underlying connection) will
// cause a major slow-down in some queries
// connection.begin();
connection.getQuadStoreConnection().setAutoCommit(true);
connection.getQuadStoreConnection().setReadOnly(readOnly);
// connection.getQuadStoreConnection().prepareCall("log_enable(2)").execute();
} catch (final Throwable ex) {
try {
connection.close();
} catch (final RepositoryException ex2) {
LOGGER.error("Cannot close connection after begin() failure", ex);
}
throw new IOException("Cannot setup read-only transaction", ex);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(this + " started in " + (readOnly ? "read-only" : "read-write")
+ " mode, " + (System.currentTimeMillis() - ts) + " ms");
}
}
private void checkWritable() {
if (this.readOnly) {
throw new IllegalStateException("Write operation not allowed on read-only transaction");
}
}
@Nullable
private CloseableIteration logClose(
@Nullable final CloseableIteration iteration) {
if (iteration == null || !LOGGER.isDebugEnabled()) {
return iteration;
}
final long ts = System.currentTimeMillis();
return new IterationWrapper(iteration) {
@Override
protected void handleClose() throws E {
try {
super.handleClose();
} finally {
LOGGER.debug("Virtuoso iteration closed after {} ms",
System.currentTimeMillis() - ts);
}
}
};
}
@Override
public CloseableIteration extends Statement, ? extends Exception> get(
@Nullable final Resource subject, @Nullable final URI predicate,
@Nullable final Value object, @Nullable final Resource context) throws IOException,
IllegalStateException {
try {
final long ts = System.currentTimeMillis();
final CloseableIteration extends Statement, ? extends Exception> result;
if (subject == null || predicate == null || object == null || context == null) {
result = logClose(this.connection.getStatements(subject, predicate, object, false,
context));
LOGGER.debug("Virtuoso getStatements() iteration obtained in {} ms",
System.currentTimeMillis() - ts);
} else {
Iterator iterator;
if (this.connection.hasStatement(subject, predicate, object, false, context)) {
iterator = Collections.emptyIterator();
} else {
iterator = Iterators.singletonIterator(new ContextStatementImpl(
subject, predicate, object, context));
}
result = new CloseableIteratorIteration(iterator);
LOGGER.debug("Virtuoso hasStatement() evaluated in {} ms",
System.currentTimeMillis() - ts);
}
return result;
} catch (final RepositoryException re) {
throw new IOException("Error while checking statement.", re);
}
}
@Override
public CloseableIteration query(final SelectQuery query,
@Nullable final BindingSet bindings, @Nullable final Long timeout)
throws DataCorruptedException, IOException, UnsupportedOperationException {
LOGGER.debug("Evaluating query:\n{}", query);
final TupleQuery tupleQuery;
try {
tupleQuery = this.connection
.prepareTupleQuery(QueryLanguage.SPARQL, query.getString());
} catch (final RepositoryException ex) {
throw new IOException("Failed to prepare SPARQL tuple query:\n" + query, ex);
} catch (final MalformedQueryException ex) {
// should not happen, as SelectQuery can only be created with valid queries
throw new UnsupportedOperationException(
"SPARQL query rejected as malformed by Virtuoso:\n" + query, ex);
}
if (bindings != null) {
for (final Binding binding : bindings) {
tupleQuery.setBinding(binding.getName(), binding.getValue());
}
}
// note: the following setting seems to be totally ignored by Virtuoso
// if (timeout != null) {
// tupleQuery.setMaxQueryTime(timeout.intValue());
// }
final int msTimeout = timeout == null ? 0 : timeout.intValue();
try {
this.connection.getQuadStoreConnection()
.prepareCall("set result_timeout = " + msTimeout).execute();
} catch (final Throwable ex) {
LOGGER.warn("Failed to set result_timeout = " + msTimeout
+ " on Virtuoso JDBC connection", ex);
}
try {
final long ts = System.currentTimeMillis();
CloseableIteration result;
result = tupleQuery.evaluate();
result = new IterationWrapper(result) {
@Override
public boolean hasNext() throws QueryEvaluationException {
try {
return super.hasNext();
} catch (final QueryEvaluationException ex) {
if (isPartialResultException(ex)) {
return false;
}
throw ex;
}
}
};
result = logClose(result);
LOGGER.debug("Virtuoso iteration obtained in {} ms", System.currentTimeMillis() - ts);
return result;
} catch (final QueryEvaluationException ex) {
if (isPartialResultException(ex)) {
return new EmptyIteration<>();
}
throw new IOException("Failed to execute query - " + ex.getMessage(), ex);
}
}
@Override
public void infer(@Nullable final Handler super Statement> handler) throws IOException,
IllegalStateException {
checkWritable();
// No inference done at this level (to be implemented in a decorator).
if (handler != null) {
try {
handler.handle(null);
} catch (final Throwable ex) {
Throwables.propagateIfPossible(ex, IOException.class);
throw new RuntimeException(ex);
}
}
}
/**
* Adds the specified RDF statement to the triple store. Virtuoso may buffer the operation,
* performing it when more opportune and in any case ensuring that the same effects are
* produced as obtainable by directly executing the operation.
*
* @param statement
* the RDF statement to add
* @throws DataCorruptedException
* in case a non-recoverable data corruption situation is detected
* @throws IOException
* in case another IO error occurs not implying data corruption
* @throws IllegalStateException
* in case the transaction is read-only
*/
public void add(final Statement statement) throws DataCorruptedException, IOException {
Preconditions.checkNotNull(statement);
checkWritable();
try {
this.connection.add(statement);
} catch (final RepositoryException ex) {
throw new IOException("Failed to add statement: " + statement, ex);
}
}
@Override
public void add(final Iterable extends Statement> stream) throws IOException,
IllegalStateException {
addBulk(stream, false);
}
/**
* Adds the specified RDF statements to the triple store. Implementations are designed to
* perform high throughput insertion.
*
* @param statements
* the RDF statements to add
* @throws DataCorruptedException
* in case a non-recoverable data corruption situation is detected
* @throws IOException
* in case another IO error occurs not implying data corruption
* @throws IllegalStateException
* in case the transaction is read-only
*/
public void addBulk(final Iterable extends Statement> statements, final boolean transaction)
throws DataCorruptedException, IOException {
Preconditions.checkNotNull(statements);
checkWritable();
try {
if (!transaction && !this.store.existsTransactionMarker()) {
this.store.addTransactionMarker();
// log_enable affects only the current transaction.
this.connection.getQuadStoreConnection().prepareCall("log_enable(2)").execute();
}
this.connection.add(statements);
this.connection.commit();
} catch (final SQLException sqle) {
throw new IllegalStateException("Invalid internal operation.", sqle);
} catch (final RepositoryException e) {
throw new DataCorruptedException("Error while adding bulk data.", e);
}
}
/**
* Removes the specified RDF statement from the triple store. Virtuoso may buffer the
* operation, performing it when more opportune and in any case ensuring that the same effects
* are produced as obtainable by directly executing the operation.
*
* @param statement
* the RDF statement to remove
* @throws DataCorruptedException
* in case a non-recoverable data corruption situation is detected
* @throws IOException
* in case another IO error occurs not implying data corruption
* @throws IllegalStateException
* in case the transaction is read-only
*/
public void remove(final Statement statement) throws DataCorruptedException, IOException {
Preconditions.checkState(!this.readOnly);
checkWritable();
try {
this.connection.remove(statement);
} catch (final RepositoryException ex) {
throw new IOException("Failed to remove statement: " + statement, ex);
}
}
@Override
public void remove(final Iterable extends Statement> stream) throws IOException,
IllegalStateException {
removeBulk(stream, false);
}
/**
* Removes the specified RDF statements from the triple store. Implementations are designed to
* perform high throughput insertion.
*
* @param statements
* the RDF statements to add
* @throws DataCorruptedException
* in case a non-recoverable data corruption situation is detected
* @throws IOException
* in case another IO error occurs not implying data corruption
* @throws IllegalStateException
* in case the transaction is read-only
*/
public void removeBulk(final Iterable extends Statement> statements,
final boolean transaction) throws DataCorruptedException, IOException {
Preconditions.checkNotNull(statements);
checkWritable();
try {
if (!transaction && !this.store.existsTransactionMarker()) {
this.store.addTransactionMarker();
// log_enable affects only the current transaction.
this.connection.getQuadStoreConnection().prepareCall("log_enable(2)").execute();
}
this.connection.remove(statements);
this.connection.commit();
} catch (final SQLException sqle) {
throw new IllegalStateException("Invalid internal operation.", sqle);
} catch (final RepositoryException e) {
throw new DataCorruptedException("Error while adding bulk data.", e);
}
}
@Override
public void end(final boolean commit) throws IOException {
final long ts = System.currentTimeMillis();
boolean committed = false;
try {
if (!this.readOnly) {
if (commit) {
try {
if (this.store.existsTransactionMarker()) {
this.connection.getQuadStoreConnection().prepareCall("log_enable(1)")
.execute();
this.store.removeTransactionMarker();
}
this.connection.commit();
committed = true;
} catch (final Throwable ex) {
try {
if (this.store.existsTransactionMarker()) {
throw new DataCorruptedException("Cannot rollback! "
+ "Modifications performed outside a transaction.");
}
this.connection.rollback();
LOGGER.debug("{} rolled back after commit failure", this);
} catch (final RepositoryException ex2) {
throw new DataCorruptedException(
"Failed to rollback transaction after commit failure", ex);
}
throw new IOException("Failed to commit transaction (rollback forced)", ex);
}
} else {
try {
this.connection.rollback();
} catch (final Throwable ex) {
throw new DataCorruptedException("Failed to rollback transaction", ex);
}
}
}
} finally {
try {
closeVirtuosoRepositoryConnection(this.connection);
} catch (final RepositoryException ex) {
LOGGER.error("Failed to close connection", ex);
} finally {
if (LOGGER.isDebugEnabled()) {
final long now = System.currentTimeMillis();
LOGGER.debug("{} {} and closed in {} ms, tx duration {} ms", this,
committed ? "committed" : "rolled back", now - ts, now - this.ts);
}
}
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
private static boolean isPartialResultException(final QueryEvaluationException ex) {
return ex.getMessage() != null && ex.getMessage().contains("Returning incomplete results");
}
private static void closeVirtuosoRepositoryConnection(
final VirtuosoRepositoryConnection connection) throws RepositoryException {
final Future> future = Data.getExecutor().schedule(new Runnable() {
@Override
public void run() {
final Connection jdbcConnection = connection.getQuadStoreConnection();
try {
final Field field = jdbcConnection.getClass().getDeclaredField("socket");
field.setAccessible(true);
final Closeable socket = (Closeable) field.get(jdbcConnection);
socket.close(); // as Virtuoso driver ignores polite interrupt requests...
LOGGER.warn("Closed socket backing virtuoso connection");
} catch (final Throwable ex) {
LOGGER.debug("Failed to close socket backing virtuoso connection "
+ "(connection class is " + jdbcConnection.getClass() + ")", ex);
}
}
}, 1000, TimeUnit.MILLISECONDS);
try {
connection.close();
} finally {
future.cancel(false);
}
}
}