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

com.blazegraph.gremlin.embedded.BlazeGraphEmbedded Maven / Gradle / Ivy

/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     [email protected]

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.blazegraph.gremlin.embedded;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.configuration.BaseConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.tinkerpop.gremlin.structure.Transaction;
import org.apache.tinkerpop.gremlin.structure.util.AbstractThreadLocalTransaction;
import org.openrdf.model.Statement;
import org.openrdf.query.BindingSet;
import org.openrdf.query.GraphQueryResult;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQueryResult;

import com.bigdata.bop.engine.IRunningQuery;
import com.bigdata.bop.engine.QueryEngine;
import com.bigdata.bop.fed.QueryEngineFactory;
import com.bigdata.journal.IIndexManager;
import com.bigdata.rdf.changesets.ChangeAction;
import com.bigdata.rdf.changesets.ChangeRecord;
import com.bigdata.rdf.changesets.IChangeRecord;
import com.bigdata.rdf.model.BigdataStatement;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.model.StatementEnum;
import com.bigdata.rdf.sail.BigdataSailBooleanQuery;
import com.bigdata.rdf.sail.BigdataSailGraphQuery;
import com.bigdata.rdf.sail.BigdataSailRepository;
import com.bigdata.rdf.sail.BigdataSailRepositoryConnection;
import com.bigdata.rdf.sail.BigdataSailTupleQuery;
import com.bigdata.rdf.sail.BigdataSailUpdate;
import com.bigdata.rdf.sail.QueryCancellationHelper;
import com.bigdata.rdf.sail.QueryCancelledException;
import com.bigdata.rdf.sail.model.RunningQuery;
import com.bigdata.rdf.sail.webapp.BigdataRDFContext.AbstractQueryTask;
import com.bigdata.rdf.sail.webapp.StatusServlet;
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.sparql.ast.eval.AST2BOpUpdate;
import com.bigdata.rdf.spo.ISPO;
import com.bigdata.rdf.store.AbstractTripleStore;
import com.bigdata.rdf.store.BigdataStatementIterator;
import com.bigdata.relation.accesspath.AbstractArrayBuffer;
import com.bigdata.striterator.ChunkedArrayIterator;
import com.bigdata.util.MillisecondTimestampFactory;
import com.blazegraph.gremlin.internal.BlazeSailListener;
import com.blazegraph.gremlin.listener.BlazeGraphEdit;
import com.blazegraph.gremlin.listener.BlazeGraphEdit.Action;
import com.blazegraph.gremlin.listener.BlazeGraphListener;
import com.blazegraph.gremlin.structure.BlazeGraph;
import com.blazegraph.gremlin.util.Code;
import com.blazegraph.gremlin.util.LambdaLogger;

/**
 * An implementation of the tinkerpop3 API that uses an embedded SAIL repository
 * instance (same JVM).
 * 

* Currently BlazeGraphEmbedded is the only concrete implementation of the * Blazegraph Tinkerpop3 API. BlazeGraphEmbedded is backed by an embedded (same * JVM) instance of Blazegraph. This puts the enterprise features of Blazegraph * (high-availability, scale-out, etc.) out of reach for the 1.0 version of the * TP3 integration, since those features are accessed via Blazegraph's * client/server API. A TP3 integration with the client/server version of * Blazegraph is reserved for a future blazegraph-tinkerpop release. *

* Blazegraph's concurrency model is MVCC, which more or less lines up with * Tinkerpop's Transaction model. When you open a BlazeGraphEmbedded instance, * you are working with the unisolated (writer) view of the database. This view * supports Tinkerpop Transactions, and reads are done against the unisolated * connection, so uncommitted changes will be visible. A BlazeGraphEmbedded can * be shared across multiple threads, but only one thread can have a Tinkerpop * Transaction open at a time (other threads will be blocked until the * transaction is closed). A TP3 Transaction is automatically opened on any read * or write operation, and automatically closed on any commit or rollback * operation. The Transaction can also be closed manually, which you will need * to do after read operations to unblock other waiting threads. *

* BlazegraphGraphEmbedded's database operations are thus single-threaded, but * Blazegraph/MVCC allows for many concurrent readers in parallel with both the * single writer and other readers. This is possible by opening a read-only view * that will read against the last commit point on the database. The read-only * view can be be accessed in parallel to the writer without any of the * restrictions described above. To get a read-only snapshot, use the following * pattern: *

*
 * final BlazeGraphEmbedded unisolated = ...; 
 * final BlazeGraphReadOnly readOnly = unisolated.readOnlyConnection(); 
 * try { 
 *     // read operations against readOnly
 * } finally { 
 *     readOnly.close(); 
 * }
 * 
*

* BlazeGraphReadOnly extends BlazeGraphEmbedded and thus offers all the same * operations, except write operations will not be permitted * (BlazeGraphReadOnly.tx() will throw an exception). You can open as many * read-only views as you like, but we recommend you use a connection pool so as * not to overtax system resources. Applications should be written with the * one-writer many-readers paradigm front of mind. *

* Important: Make sure to close the read-only view as soon as you are done with * it. *

* @author mikepersonick */ public class BlazeGraphEmbedded extends BlazeGraph { private final transient static LambdaLogger log = LambdaLogger.getLogger(BlazeGraphEmbedded.class); static { /** * We do not want auto-commit for SPARQL Update. * * TODO FIXME Make this a configurable property. */ AST2BOpUpdate.AUTO_COMMIT = false; } /** * Open a BlazeGraphEmbedded (unisolated) instance wrapping the provided * SAIL repository with no additional configuration options. * * @param repo * an open and initialized repository * @return * BlazeGraphEmbedded instance */ public static BlazeGraphEmbedded open(final BigdataSailRepository repo) { return open(repo, new BaseConfiguration()); } /** * Open a BlazeGraphEmbedded (unisolated) instance wrapping the provided * SAIL repository and using the supplied configuration. * * @return * an open and initialized repository * @param config * additional configuration * @return * instance */ public static BlazeGraphEmbedded open(final BigdataSailRepository repo, final Configuration config) { Objects.requireNonNull(repo); if (!repo.getDatabase().isStatementIdentifiers()) { throw new IllegalArgumentException("BlazeGraph/TP3 requires statement identifiers."); } /* * Grab the last commit time and also check for clock skew. */ final long lastCommitTime = lastCommitTime(repo); config.setProperty(BlazeGraph.Options.LIST_INDEX_FLOOR, lastCommitTime); return new BlazeGraphEmbedded(repo, config); } /** * Take a quick peek at the last commit time on the supplied journal file. * If it's ahead of our system time, then use that last commit time as a * lower bound on blaze's transaction timestamps. Otherwise just use the * system time as usual. Guards against clock skew (sharing journals * between machines with different clock times). */ protected static long lastCommitTime(final BigdataSailRepository repo) { final long systemTime = System.currentTimeMillis(); log.debug(() -> "temporarily setting lower bound to Long.MAX_VALUE: " + (Long.MAX_VALUE - 100)); /* * Temporarily set the lower bound to something way way in the future in * case the journal really is ahead of our system clock. */ MillisecondTimestampFactory.setLowerBound(Long.MAX_VALUE - 100); /* * Pull the last commit time from the journal. */ // final long lastCommitTime = // journal.getRootBlockView().getLastCommitTime(); final long lastCommitTime = repo.getDatabase().getIndexManager().getLastCommitTime(); final long lowerBound; if (lastCommitTime > 0l && systemTime < lastCommitTime) { log.info(() -> "found clock skew, using last commit time: " + lastCommitTime); /* * If the journal has a last commit time and if that last commit * time is in the future relative to our system clock, use the last * commit time as a lower bound on blaze transaction timestamps. */ lowerBound = lastCommitTime; } else { log.debug(() -> "setting lower bound to system time as normal: " + systemTime); /* * Otherwise just use the system time as we normally would. */ lowerBound = systemTime; } MillisecondTimestampFactory.setLowerBound(lowerBound); return lowerBound; } /** * Embedded SAIL repository. */ protected final BigdataSailRepository repo; /** * Graph listeners. * * @see BlazeGraphListener */ protected final List listeners = new CopyOnWriteArrayList<>(); /** * Tinkerpop3 transaction objects - manages use of the unisolated * BigdataSailRepositoryConnection. */ private final BlazeTransaction tx = new BlazeTransaction(); /** * Listen for change events from the SAIL (RDF), convert to PG data * ({@link BlazeGraphEdit}s), forward notifications to * {@link BlazeGraphListener}s. */ protected final ChangeLogTransformer listener = new ChangeLogTransformer(); /** * Hidden constructor - use {@link #open(BigdataSailRepository, Configuration)}. * * @param repo * an open and initialized repository * @param config * additional configuration */ protected BlazeGraphEmbedded(final BigdataSailRepository repo, final Configuration config) { super(config); this.repo = repo; } /** * Open a read-only view of the data at the last commit point. The read view * can be used for read operations in parallel with the write view (this * view) and other read views. Read views do not supports Tinkerpop * Transactions or write operations. *

* These should be bounded in number by your application and always * closed when done using them. *

* * @return * a read-only view on the last commit point */ public BlazeGraphReadOnly readOnlyConnection() { if (closed) throw Exceptions.alreadyClosed(); final BigdataSailRepositoryConnection cxn = Code.wrapThrow(() -> repo.getReadOnlyConnection()); return new BlazeGraphReadOnly(repo, cxn, config); } /** * Return the {@link BlazeTransaction} instance. */ @Override public BlazeTransaction tx() { if (closed) throw Exceptions.alreadyClosed(); return tx; } /** * Tinkerpop3 Transaction object. Wraps access to the unsiolated SAIL * connection in a thread local. Access will be re-entrant, but the * unisolated connection cannot be shared across multiple threads. Threads * attempting to access the unisolated SAIL connection (via tx().open(), * which happens automatically on any write or read operation) will be * blocked by the connection owner thread until that thread closes it (via * tx().close(), which happens automatically on any commit or rollback * operation). * * @author mikepersonick */ public class BlazeTransaction extends AbstractThreadLocalTransaction { private final ThreadLocal tlTx = ThreadLocal.withInitial(() -> null); private BlazeTransaction() { super(BlazeGraphEmbedded.this); } /** * True if this thread has the unisolated connection open. */ @Override public boolean isOpen() { return (tlTx.get() != null); } /** * Grab the unisolated SAIL connection and attach the {@link ChangeLogTransformer} * to it. This operation will block if the unisolated connection is * being held open by another thread. */ @Override protected void doOpen() { Code.wrapThrow(() -> { final BigdataSailRepositoryConnection cxn = repo.getUnisolatedConnection(); cxn.addChangeLog(listener); tlTx.set(cxn); }); } /** * Commit and close the unisolated connection (if open). */ @Override protected void doCommit() throws TransactionException { final BigdataSailRepositoryConnection cxn = tlTx.get(); if (cxn != null) { try { cxn.commit(); } catch (Exception ex) { throw new TransactionException(ex); } finally { /* * Close the connection on commit per the required * semantics of Tinkerpop3. */ close(cxn); } } } /** * Rollback and close the unisolated connection (if open). */ @Override protected void doRollback() throws TransactionException { final BigdataSailRepositoryConnection cxn = tlTx.get(); if (cxn != null) { try { cxn.rollback(); } catch (Exception ex) { throw new TransactionException(ex); } finally { /* * Close the connection on rollback. Not safe to re-use. */ close(cxn); } } } /** * Close the unisolated connection (if open), releasing it for use * by other threads. */ @Override protected void doClose() { super.doClose(); closeInternal(); } /** * Release the unisolated connection. */ private void closeInternal() { final BigdataSailRepositoryConnection cxn = tlTx.get(); if (cxn != null) { close(cxn); } } /** * Internal close operation. Remove the change listener, remove * the connection from the {@link ThreadLocal}, close the connection. */ private void close(final BigdataSailRepositoryConnection cxn) { cxn.removeChangeLog(listener); tlTx.remove(); Code.wrapThrow(() -> cxn.close()); } /** * Direct access to the unisolated connection. May return null if the * connection has not been opened yet by this thread. * * @return unisolated {@link BigdataSailRepositoryConnection} */ public BigdataSailRepositoryConnection cxn() { return tlTx.get(); } /** * Flush the statement buffers to the indices without committing. */ public void flush() { final BigdataSailRepositoryConnection cxn = tlTx.get(); if (cxn != null) { Code.wrapThrow(() -> cxn.flush()); } } } /** * Listen for change events from the SAIL (RDF), convert to PG data * ({@link BlazeGraphEdit}s), forward notifications to * {@link BlazeGraphListener}s. */ private class ChangeLogTransformer implements BlazeSailListener { /** * We need to buffer and materialize these, since remove events come in * with unmaterialized values. Default buffer size is 1000. */ private final AbstractArrayBuffer records = new AbstractArrayBuffer(1000, IChangeRecord.class, null) { @Override protected long flush(final int n, final IChangeRecord[] a) { /* * Materialize, notify, close. */ try (Stream s = materialize(n, a)) { s.forEach(ChangeLogTransformer.this::notify); } return n; } }; /** * Changed events coming from bigdata. */ @Override public void changeEvent(final IChangeRecord record) { if (listeners.isEmpty()) return; /* * Watch out for history change events. */ if (record.getStatement().getStatementType() == StatementEnum.History) { return; } /* * Adds come in already materialized. Removes do not. We batch and * materialize removes in bulk. */ records.add(record); } /** * Turn a change record into a graph edit and notify the graph listeners. * * @param record * Bigdata change record. */ protected void notify(final IChangeRecord record) { if (listeners.isEmpty()) return; /* * Some RDF statements do not map to PG graph edits. */ toGraphEdit(record).ifPresent(edit -> listeners.forEach(listener -> listener.graphEdited(edit, record.toString())) ); } /** * Turn a bigdata change record into a graph edit. Some RDF statements * do not map to PG graph edits. * * @param record * Blaze RDF change event * @return * PG graph edit */ protected Optional toGraphEdit(final IChangeRecord record) { final Action action; if (record.getChangeAction() == ChangeAction.INSERTED) { action = Action.Add; } else if (record.getChangeAction() == ChangeAction.REMOVED) { action = Action.Remove; } else { /* * Truth maintenance. */ return Optional.empty(); } return graphAtomTransform().apply(record.getStatement()) .map(atom -> new BlazeGraphEdit(action, atom)); } /** * Materialize a batch of change records. You MUST close this stream * when done. * * @param n * number of valid elements in the array * @param a * array of {@link IChangeRecord}s * @return * Same records with materialized values */ protected Stream materialize(final int n, final IChangeRecord[] a) { final AbstractTripleStore db = cxn().getTripleStore(); /* * Collect up unmaterialized ISPOs out of the change records * (only the removes are unmaterialized). */ int nRemoves = 0; final ISPO[] spos = new ISPO[n]; for (int i = 0; i < n; i++) { if (a[i].getChangeAction() == ChangeAction.REMOVED) spos[nRemoves++] = a[i].getStatement(); } /* * Use the database to resolve them into BigdataStatements */ final BigdataStatementIterator it = db .asStatementIterator(new ChunkedArrayIterator(nRemoves, spos, null/* keyOrder */)); /* * Stream the records, replacing removes with materialized versions * of same. We can do this because the iterator above is order- * preserving. */ return Arrays.stream(a, 0, n).onClose(() -> it.close()) .map(r -> { if (r.getChangeAction() == ChangeAction.REMOVED) { final BigdataStatement stmt = it.next(); return new ChangeRecord(stmt, ChangeAction.REMOVED); } else { return r; } }); } /** * Notification of transaction committed. */ @Override public void transactionCommited(final long commitTime) { if (listeners.isEmpty()) { records.reset(); } else { records.flush(); listeners.forEach(l -> l.transactionCommited(commitTime)); } } /** * Notification of transaction aborted. */ @Override public void transactionAborted() { if (listeners.isEmpty()) { records.reset(); } else { records.flush(); listeners.forEach(BlazeGraphListener::transactionAborted); } } @Override public void close() { records.reset(); } } /** * Return the unisolated SAIL connection. Automatically opens the * Tinkerpop3 Transaction if not already open. */ @Override public BigdataSailRepositoryConnection cxn() { if (closed) throw Exceptions.alreadyClosed(); tx.readWrite(); return tx.cxn(); } /** * Pass through to tx().commit(). * * @see BlazeTransaction#commit */ public void commit() { if (closed) throw Exceptions.alreadyClosed(); tx().commit(); } /** * Pass through to tx().rollbakc(). * * @see BlazeTransaction#rollback */ public void rollback() { if (closed) throw Exceptions.alreadyClosed(); tx().rollback(); } /** * Pass through to tx().flush(). * * @see BlazeTransaction#flush */ public void flush() { if (closed) throw Exceptions.alreadyClosed(); tx().flush(); } /** * Close the unisolated connection if open and close the repository. Default * close behavior is to roll back any uncommitted changes. * * @see Transaction.CLOSE_BEHAVIOR */ protected volatile boolean closed = false; @Override public synchronized void close() { if (closed) return; try { /* * Try with the auto-close behavior first. */ tx.close(); } catch (Exception ex) { /* * But if auto-close is set to Transaction.CLOSE_BEHAVIOR.MANUAL * then just release the connection manually. */ tx.closeInternal(); } Code.wrapThrow(() -> repo.shutDown()); closed = true; } /** * Add a {@link BlazeGraphListener}. * * @param listener the listener */ public void addListener(final BlazeGraphListener listener) { this.listeners.add(listener); } /** * Remove a {@link BlazeGraphListener}. * * @param listener the listener */ public void removeListener(final BlazeGraphListener listener) { this.listeners.remove(listener); } /** * Return the RDF value factory. * * @see BigdataValueFactory */ public BigdataValueFactory rdfValueFactory() { return (BigdataValueFactory) repo.getValueFactory(); } /** * Return the Sparql query engine. */ private QueryEngine getQueryEngine() { final IIndexManager ndxManager = getIndexManager(); final QueryEngine queryEngine = (QueryEngine) QueryEngineFactory.getInstance().getQueryController(ndxManager); return queryEngine; } /** * Return the database index manager. */ private IIndexManager getIndexManager() { return repo.getDatabase().getIndexManager(); } /** * {@inheritDoc} * * Logs the query at INFO and logs the optimized AST at TRACE. */ @Override protected Stream _select( final String queryStr, final String extQueryId) { logQuery(queryStr); return Code.wrapThrow(() -> { final BigdataSailTupleQuery query = (BigdataSailTupleQuery) cxn().prepareTupleQuery(QueryLanguage.SPARQL, queryStr); setMaxQueryTime(query); final UUID queryId = setupQuery(query.getASTContainer(), QueryType.SELECT, extQueryId); sparqlLog.trace(() -> "optimized AST:\n"+query.optimize()); /* * Result is closed automatically by GraphStreamer. */ final TupleQueryResult result = query.evaluate(); final Optional onClose = Optional.of(() -> finalizeQuery(queryId)); return new GraphStreamer<>(result, onClose).stream(); }); } /** * {@inheritDoc} * * Logs the query at INFO and logs the optimized AST at TRACE. */ @Override protected Stream _project( final String queryStr, final String extQueryId) { logQuery(queryStr); return Code.wrapThrow(() -> { final BigdataSailGraphQuery query = (BigdataSailGraphQuery) cxn().prepareGraphQuery(QueryLanguage.SPARQL, queryStr); setMaxQueryTime(query); final UUID queryId = setupQuery(query.getASTContainer(), QueryType.CONSTRUCT, extQueryId); sparqlLog.trace(() -> "optimized AST:\n"+query.optimize()); /* * Result is closed automatically by GraphStreamer. */ final GraphQueryResult result = query.evaluate(); final Optional onClose = Optional.of(() -> finalizeQuery(queryId)); return new GraphStreamer<>(result, onClose).stream(); }); } /** * {@inheritDoc} * * Logs the query at INFO. */ @Override protected boolean _ask( final String queryStr, final String extQueryId) { logQuery(queryStr); return Code.wrapThrow(() -> { /* try */ final BigdataSailBooleanQuery query = (BigdataSailBooleanQuery) cxn().prepareBooleanQuery(QueryLanguage.SPARQL, queryStr); setMaxQueryTime(query); final UUID queryId = setupQuery(query.getASTContainer(), QueryType.ASK, extQueryId); // sparqlLog.trace(() -> "optimized AST:\n"+query.optimize()); try { return query.evaluate(); } finally { finalizeQuery(queryId); } }); } /** * {@inheritDoc} * * Logs the query at INFO. */ @Override protected void _update( final String queryStr, final String extQueryId) { logQuery(queryStr); Code.wrapThrow(() -> { final BigdataSailUpdate query = (BigdataSailUpdate) cxn().prepareUpdate(QueryLanguage.SPARQL, queryStr); final UUID queryId = setupQuery(query.getASTContainer(), null /* QueryType.UPDATE */, extQueryId); try { query.execute(); } finally { finalizeQuery(queryId); } }); } /** * Chop queries at a max length for logging. */ private void logQuery(final String queryStr) { sparqlLog.info(() -> "query:\n"+ (queryStr.length() <= sparqlLogMax ? queryStr : queryStr.substring(0, sparqlLogMax)+" ...")); } /** * Dump all the statements in the store to a String. Bypasses the SAIL, * so you must call {@link #flush()} to get an accurate picture. */ public String dumpStore() throws Exception { return repo.getDatabase().dumpStore().toString(); } /** * Count all the statements in the store. Bypasses the SAIL, * so you must call {@link #flush()} to get an accurate picture. */ public long statementCount() throws Exception { return repo.getDatabase().getStatementCount(true); } /** * Count all the statements in the store including deleted statements. * Bypasses the SAIL, so you must call {@link #flush()} to get an accurate * picture. */ public long historicalStatementCount() throws Exception { return repo.getDatabase().getStatementCount(false); } //////////////////////////////////////////////////////////////////////// // Query Cancellation Section //////////////////////////////////////////////////////////////////////// /** * *

* Note: This is also responsible for noticing the time at which the * query begins to execute and storing the {@link RunningQuery} in the * {@link #queries} map. * * @param The connection. */ private UUID setupQuery(final ASTContainer astContainer, final QueryType queryType, final String extId) { // Note the begin time for the query. final long begin = System.nanoTime(); // Figure out the UUID under which the query will execute. final UUID queryUuid = setQueryId(astContainer, UUID.randomUUID()); //Set to UUID of internal ID if it is null. final String extQueryId = extId == null?queryUuid.toString():extId; if (log.isDebugEnabled() && extId == null) { log.debug("Received null external query ID. Using " + queryUuid.toString()); } final boolean isUpdateQuery = queryType != QueryType.ASK && queryType != QueryType.CONSTRUCT && queryType != QueryType.DESCRIBE && queryType != QueryType.SELECT; final RunningQuery r = new RunningQuery(extQueryId, queryUuid, begin, isUpdateQuery); // Stuff it in the maps of running queries. queries.put(extQueryId, r); queries2.put(queryUuid, r); if (log.isDebugEnabled()) { log.debug("Setup Query (External ID, UUID): ( " + extQueryId + " , " + queryUuid + " )"); log.debug("External query for " + queryUuid + " is :\n" + getQuery(queryUuid).getExtQueryId()); log.debug(runningQueriesToString()); } return queryUuid; } /** * Determines the {@link UUID} which will be associated with the * {@link IRunningQuery}. If {@link QueryHints#QUERYID} has already been * used by the application to specify the {@link UUID} then that * {@link UUID} is noted. Otherwise, a random {@link UUID} is generated and * assigned to the query by binding it on the query hints. *

* Note: The ability to provide metadata from the {@link ASTContainer} in * the {@link StatusServlet} or the "EXPLAIN" page depends on the ability to * cross walk the queryIds as established by this method. * * @param query * The query. * * @param queryUuid * * @return The {@link UUID} which will be associated with the * {@link IRunningQuery} and never null. */ private UUID setQueryId(final ASTContainer astContainer, UUID queryUuid) { // Figure out the effective UUID under which the query will run. final String queryIdStr = astContainer.getQueryHint(QueryHints.QUERYID); if (queryIdStr == null) { // Not specified, so generate and set on query hint. queryUuid = UUID.randomUUID(); } astContainer.setQueryHint(QueryHints.QUERYID, queryUuid.toString()); return queryUuid; } /** * Wrapper method to clean up query and throw exception is interrupted. * * @param queryId * @throws QueryCancelledException */ private void finalizeQuery(final UUID queryId) throws QueryCancelledException { if (queryId == null) return; // 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); } } /** * The currently executing queries (does not include queries where a client * has established a connection but the query is not running because the * {@link #queryService} is blocking). *

* Note: This includes both SPARQL QUERY and SPARQL UPDATE requests. * However, the {@link AbstractQueryTask#queryUuid} might not yet be bound * since it is not set until the request begins to execute. See * {@link AbstractQueryTask#setQueryId(ASTContainer)}. */ private static final ConcurrentHashMap queries = new ConcurrentHashMap<>(); /** * The currently executing QUERY and UPDATE requests. *

* Note: This does not include requests where a client has established a * connection to the SPARQL end point but the request is not executing * because the {@link #queryService} is blocking). *

* Note: This collection was introduced because the SPARQL UPDATE requests * are not executed on the {@link QueryEngine} and hence we can not use * {@link QueryEngine#getRunningQuery(UUID)} to resolve the {@link Future} */ private static final ConcurrentHashMap queries2 = new ConcurrentHashMap<>(); /** * Get a running query by internal query id. */ private RunningQuery getQuery(final UUID queryUuid) { return queries2.get(queryUuid); } /** * Remove the query from the internal queues. */ private void tearDownQuery(UUID queryId) { if (queryId != null) { if(log.isDebugEnabled()) { log.debug("Tearing down query: " + queryId ); log.debug("queries2 has " + queries2.size()); } final RunningQuery r = queries2.get(queryId); if (r != null) { queries.remove(r.getExtQueryId(), r); queries2.remove(queryId); if (log.isDebugEnabled()) { log.debug("Tearing down query: " + queryId); log.debug("queries2 has " + queries2.size()); } } } } /** * Helper method to determine if a query was cancelled. */ private boolean isQueryCancelled(final UUID queryId) { if (log.isDebugEnabled()) { log.debug(queryId); } final RunningQuery q = getQuery(queryId); if (log.isDebugEnabled() && q != null) { log.debug(queryId + " isCancelled: " + q.isCancelled()); } if (q != null) { return q.isCancelled(); } return false; } /** * Return a list of all running queries in string form. */ public String runningQueriesToString() { return queries2.values().stream() .map(r -> r.getQueryUuid() + " : \n" + r.getExtQueryId()) .collect(Collectors.joining("\n")); } /** * Return a list of all running queries. */ @Override public Collection getRunningQueries() { return queries2.values(); } /** * Cancel a running query by internal id. */ @Override public void cancel(final UUID queryId) { Objects.requireNonNull(queryId); QueryCancellationHelper.cancelQuery(queryId, this.getQueryEngine()); final RunningQuery q = getQuery(queryId); if(q != null) { //Set the status to cancelled in the internal queue. q.setCancelled(true); } } /** * Cancel a running query. */ @Override public void cancel(final RunningQuery rQuery) { if (rQuery != null) { final UUID queryId = rQuery.getQueryUuid(); cancel(queryId); } } /** * DO NOT INVOKE FROM APPLICATION CODE - this method * deletes the KB instance and destroys the backing database instance. It is * used to help tear down unit tests. */ public void __tearDownUnitTest() { repo.getSail().__tearDownUnitTest(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy