All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.fbk.knowledgestore.triplestore.virtuoso.VirtuosoTripleTransaction Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 1.7.1
Show newest version
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 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 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 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 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 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 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 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);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy