/**
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.rdf.sparql.ast.service.storedquery;
import java.util.Arrays;
import java.util.concurrent.Future;
import org.apache.log4j.Logger;
import org.openrdf.query.BindingSet;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.TupleQueryResult;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.sail.BigdataSailRepositoryConnection;
import com.bigdata.rdf.sail.BigdataSailTupleQuery;
import com.bigdata.rdf.sail.Sesame2BigdataIterator;
import com.bigdata.rdf.sparql.ast.eval.ASTEvalHelper;
import com.bigdata.rdf.sparql.ast.eval.AbstractServiceFactoryBase;
import com.bigdata.rdf.sparql.ast.eval.ServiceParams;
import com.bigdata.rdf.sparql.ast.service.ExternalServiceCall;
import com.bigdata.rdf.sparql.ast.service.IServiceOptions;
import com.bigdata.rdf.sparql.ast.service.OpenrdfNativeServiceOptions;
import com.bigdata.rdf.sparql.ast.service.ServiceCallCreateParams;
import com.bigdata.rdf.sparql.ast.service.ServiceNode;
import com.bigdata.rdf.sparql.ast.service.ServiceRegistry;
import com.bigdata.rdf.store.AbstractTripleStore;
import com.bigdata.rdf.task.AbstractApiTask;
import cutthecrap.utils.striterators.ICloseableIterator;
/**
* A SERVICE that exposes a stored query for execution. The stored query may be
* a SPARQL query or arbitrary procedural application logic, but it must
* evaluate to a solution multi-set. The service interface is written to the
* openrdf interfaces in order to remove the burden of dealing with bigdata
* {@link IV}s from the application.
* <p>
* In order to use a stored query, a concrete instance of this class must be
* registered against the {@link ServiceRegistry}. The choice of the SERVICE URI
* is up to the application. The effective value of the baseURI during query
* evaluation will be the SERVICE URI.
*
* <pre>
* final URI serviceURI = new URIImpl(
* "http://www.bigdata.com/rdf/stored-query#my-stored-query");
*
* ServiceRegistry.getInstance().add(serviceURI, new MyStoredQueryService());
* </pre>
*
* Thereafter, the stored query may be referenced from SPARQL using its assigned
* service URI:
*
* <pre>
* SELECT * {
* SERVICE <http://www.bigdata.com/rdf/stored-query#my-stored-query> { }
* }
* </pre>
*
* The SERVICE invocation may include a group graph pattern that will be parsed
* and made accessible to the stored query service as a {@link ServiceParams}
* object. For example:
*
* <pre>
* SELECT * {
* SERVICE <http://www.bigdata.com/rdf/stored-query#my-stored-query> {
* bd:serviceParam :color :"blue" .
* bd:serviceParam :color :"green" .
* bd:serviceParam :size :"large" .
* }
* }
* </pre>
*
* will provide the stored query with two bindings for the
* <code>:color = {"blue", "green"}</code> and one binding for
* <code>:size = {"large"}</code>. The value key names, the allowed value types
* for each key name, and the interpretation of those values are all specific to
* a given stored query service implementation class. They will be provided to
* that class as a {@link ServiceParams} object.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*
* @see <a href="http://trac.blazegraph.com/ticket/989">Stored Query Service</a>
*
* FIXME Wiki page.
*
* FIXME Generalize to support groovy scripting.
*
* TODO We could use {@link ASTEvalHelper} to evaluate at the bigdata level
* without forcing the materialization of any variable bindings from the
* lexicon indices. This would be faster for some purposes, especially if
* the stored procedure is only used to JOIN into an outer query as in
* <code>SELECT * { SERVICE bsq:my-service {} }</code>
*/
abstract public class StoredQueryService extends AbstractServiceFactoryBase {
public interface Options {
// /**
// * The namespace used for stored query service.
// */
// String NAMESPACE = "http://www.bigdata.com/rdf/stored-query#";
}
static private transient final Logger log = Logger
.getLogger(StoredQueryService.class);
private final OpenrdfNativeServiceOptions serviceOptions;
public StoredQueryService() {
serviceOptions = new OpenrdfNativeServiceOptions();
}
@Override
public IServiceOptions getServiceOptions() {
return serviceOptions;
}
@Override
final public ExternalServiceCall create(final ServiceCallCreateParams params) {
if (params == null)
throw new IllegalArgumentException();
final AbstractTripleStore store = params.getTripleStore();
if (store == null)
throw new IllegalArgumentException();
final ServiceNode serviceNode = params.getServiceNode();
if (serviceNode == null)
throw new IllegalArgumentException();
final ServiceParams serviceParams = ServiceParams
.gatherServiceParams(params);
return create(params, serviceParams);
}
public ExternalServiceCall create(
final ServiceCallCreateParams createParams,
final ServiceParams serviceParams) {
/*
* Create and return the ServiceCall object which will execute this
* query.
*/
return new StoredQueryServiceCall(createParams, serviceParams);
}
/**
* Abstract method for core application logic. The implementation may
* execute a SPARQL query, or a series or SPARQL or other operations under
* application control.
*
* @param cxn
* The connection that should be used to read on the SPARQL
* database. The connection will be closed by the caller.
* @param createParams
* The SERVICE creation parameters.
* @param serviceParams
* The SERVICE invocation parameters.
* @return The solution multi-set.
*
* @throws Exception
*/
abstract protected TupleQueryResult doQuery(
final BigdataSailRepositoryConnection cxn,
final ServiceCallCreateParams createParams,
final ServiceParams serviceParams) throws Exception;
private class StoredQueryServiceCall implements ExternalServiceCall {
private final ServiceCallCreateParams createParams;
private final ServiceParams serviceParams;
public StoredQueryServiceCall(
final ServiceCallCreateParams createParams,
final ServiceParams serviceParams) {
if (createParams == null)
throw new IllegalArgumentException();
if (serviceParams == null)
throw new IllegalArgumentException();
this.createParams = createParams;
this.serviceParams = serviceParams;
}
@Override
public IServiceOptions getServiceOptions() {
return createParams.getServiceOptions();
}
@Override
public ICloseableIterator<BindingSet> call(
final BindingSet[] bindingSets) throws Exception {
if (log.isInfoEnabled()) {
log.info(bindingSets.length);
log.info(Arrays.toString(bindingSets));
log.info(serviceParams);
}
final AbstractTripleStore tripleStore = createParams
.getTripleStore();
final Future<TupleQueryResult> ft = AbstractApiTask.submitApiTask(
tripleStore.getIndexManager(),
new StoredQueryTask(tripleStore.getNamespace(), tripleStore
.getTimestamp(), bindingSets));
try {
final TupleQueryResult tupleQueryResult = ft.get();
return new Sesame2BigdataIterator<BindingSet, QueryEvaluationException>(
tupleQueryResult);
} finally {
ft.cancel(true/* mayInterruptIfRunning */);
}
}
/**
* Task to execute the stored query.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan
* Thompson</a>
*/
private class StoredQueryTask extends AbstractApiTask<TupleQueryResult> {
/**
*
* FIXME This is ignoring the exogenous bindings. This is more or
* less equivalent to bottom-up evaluation. It would be more
* efficient if we could flow in the exogenous bindings but this is
* not supported before openrdf 2.7 (we hack this in
* {@link BigdataSailTupleQuery}).
*/
private final BindingSet[] bindingSets;
public StoredQueryTask(final String namespace,
final long timestamp, final BindingSet[] bindingSets) {
super(namespace, timestamp);
this.bindingSets = bindingSets;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public TupleQueryResult call() throws Exception {
BigdataSailRepositoryConnection cxn = null;
boolean success = false;
try {
// Note: Will be UPDATE connection if UPDATE request!!!
cxn = getQueryConnection();
if (log.isTraceEnabled())
log.trace("Query running...");
final TupleQueryResult ret = doQuery(cxn, createParams,
serviceParams);
success = true;
if (log.isTraceEnabled())
log.trace("Query done.");
return ret;
} finally {
if (cxn != null) {
if (!success && !cxn.isReadOnly()) {
/*
* Force rollback of the connection.
*
* Note: It is possible that the commit has already
* been processed, in which case this rollback()
* will be a NOP. This can happen when there is an
* IO error when communicating with the client, but
* the database has already gone through a commit.
*/
try {
// Force rollback of the connection.
cxn.rollback();
} catch (Throwable t) {
log.error(t, t);
}
}
try {
// Force close of the connection.
cxn.close();
} catch (Throwable t) {
log.error(t, t);
}
}
}
}
} // StoredQueryApiTask
} // StoredQueryServiceCall
} // StoredQueryService