/*******************************************************************************
* Copyright (c) 2007 Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* File: $Source$
* Created by: Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>)
* Created on: Oct 29, 2007
* Revision: $Id$
*
* Contributors:
* Cambridge Semantics Incorporated - initial API and implementation
*******************************************************************************/
package org.openanzo.datasource.nodecentric.internal;
import java.io.IOException;
import java.io.Writer;
import java.sql.Connection;
import java.util.HashSet;
import java.util.Map;
import org.openanzo.analysis.RequestAnalysis;
import org.openanzo.cache.ICacheProvider;
import org.openanzo.datasource.IDatasource;
import org.openanzo.datasource.nodecentric.query.ServerEngineConfig;
import org.openanzo.datasource.nodecentric.query.ServerSolutionGenerator;
import org.openanzo.datasource.nodecentric.query.predicates.TextLikePredicate;
import org.openanzo.datasource.services.BaseQueryService;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.AnzoRuntimeException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.glitter.Engine;
import org.openanzo.glitter.dataset.QueryDataset;
import org.openanzo.glitter.exception.GlitterParseException;
import org.openanzo.glitter.query.PatternSolution;
import org.openanzo.glitter.query.QueryResults;
import org.openanzo.glitter.query.SolutionGenerator;
import org.openanzo.glitter.query.TextMatchPredicate;
import org.openanzo.glitter.syntax.concrete.ParseException;
import org.openanzo.jdbc.query.IRdbValue;
import org.openanzo.jdbc.utils.RdbException;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Value;
import org.openanzo.services.IOperationContext;
import org.openanzo.services.ISolutionGeneratorProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>)
*
*/
public class NodeCentricQueryService extends BaseQueryService implements ISolutionGeneratorProvider {
private static final Logger log = LoggerFactory.getLogger(NodeCentricQueryService.class);
private final NodeCentricDatasource datasource;
ServerEngineConfig prepopulatedConfig = new ServerEngineConfig(true);
ServerEngineConfig config = new ServerEngineConfig(false);
Engine engine;
/**
* Create QueryService
*
* @param datasource
* parent datasource
* @param cacheProvider
* cache provider
*/
public NodeCentricQueryService(NodeCentricDatasource datasource, ICacheProvider cacheProvider) {
this.datasource = datasource;
initCache(cacheProvider);
config.registerFunctionalPredicate(org.openanzo.glitter.util.Constants.TEXTMATCHPREDICATE, TextMatchPredicate.class);
config.registerFunctionalPredicate(org.openanzo.glitter.util.Constants.TEXTLIKEPREDICATE, TextLikePredicate.class);
engine = new Engine(config);
}
public IDatasource getDatasource() {
return datasource;
}
public SolutionGenerator acquireSolutionGenerator(IOperationContext context) {
try {
NodeCentricOperationContext connectionContext = datasource.getQueryContext(context);
return acquireSolutionGenerator(connectionContext);
} catch (AnzoException e) {
throw new AnzoRuntimeException(e);
}
}
private final ThreadLocal<Integer> transactionDepth = new ThreadLocal<Integer>();
private SolutionGenerator acquireSolutionGenerator(NodeCentricOperationContext context) {
try {
Integer depth = transactionDepth.get();
int d = 0;
if (depth != null)
d = depth.intValue();
log.info(LogUtils.RDB_MARKER, "begin transaction depth: {}", d);
if (d == 0) {
log.info(LogUtils.RDB_MARKER, "beginning transaction: {}", context);
datasource.begin(context.getConnection(), false, false);
}
transactionDepth.set(d++);
ServerSolutionGenerator solutionGenerator = prepopulatedConfig.getSolutionGeneratorFactory().getSolutionGenerator();
solutionGenerator.setContext(context);
return solutionGenerator;
} catch (RdbException e) {
throw new AnzoRuntimeException(ExceptionConstants.SERVER.QUERY_FAILED_ERROR, e);
} catch (AnzoException e) {
throw new AnzoRuntimeException(e);
}
}
public void releaseSolutionGenerator(SolutionGenerator solutionGenerator) throws AnzoException {
ServerSolutionGenerator generator = (ServerSolutionGenerator) solutionGenerator;
NodeCentricOperationContext context = generator.getContext();
Integer depth = transactionDepth.get();
if (depth == null)
depth = 0;
else
transactionDepth.set(depth--);
log.info(LogUtils.RDB_MARKER, "complete transaction depth: {}", depth);
if (depth <= 0) {
log.info(LogUtils.RDB_MARKER, "commiting transaction: {}", context);
datasource.commit(context.getConnection(), false, false);
transactionDepth.set(0);
}
if (context != null) {
datasource.returnQueryContext(context);
}
}
@Override
protected QueryResults executeQueryInternal(IOperationContext context, QueryDataset uriSet, String query, URI baseUri) throws AnzoException {
log.info(LogUtils.RDB_MARKER, "Query received: {}", query);
NodeCentricOperationContext connectionContext = null;
try {
long start = -1;
if (RequestAnalysis.isAnalysisEnabled()) {
start = System.currentTimeMillis();
}
connectionContext = datasource.getQueryContext(context);
ServerSolutionGenerator solutionGenerator = config.getSolutionGeneratorFactory().getSolutionGenerator();
solutionGenerator.setContext(connectionContext);
try {
boolean isMine = !datasource.isInTransaction(connectionContext.getConnection());
if (isMine)
datasource.begin(connectionContext.getConnection(), false, false);
if (RequestAnalysis.isAnalysisEnabled()) {
long end = System.currentTimeMillis();
RequestAnalysis.addAnalysisProperty("queryGetConnection", (end - start));
}
try {
if (RequestAnalysis.isAnalysisEnabled()) {
start = System.currentTimeMillis();
}
QueryResults executeQuery = engine.executeQuery(solutionGenerator, query, uriSet, baseUri);
if (RequestAnalysis.isAnalysisEnabled()) {
long end = System.currentTimeMillis();
RequestAnalysis.addAnalysisProperty("glitterExecuteQuery", (end - start));
}
// populate all nodes before returning connections
// TODO: this should be done as a bulk operation for all the nodes that need to be populated
if (RequestAnalysis.isAnalysisEnabled()) {
start = System.currentTimeMillis();
}
if (executeQuery.isSelectResult()) {
HashSet<Long> ids = new HashSet<Long>();
for (PatternSolution sol : executeQuery.getSelectResults()) {
for (int i = 0; i < sol.size(); i++) {
if (sol.getBinding(i) instanceof IRdbValue && !((IRdbValue) sol.getBinding(i)).populated()) {
ids.add(((IRdbValue) sol.getBinding(i)).getId());
}
if (sol.getValue(i) instanceof IRdbValue && !((IRdbValue) sol.getValue(i)).populated()) {
ids.add(((IRdbValue) sol.getValue(i)).getId());
}
}
}
Map<Long, Value> values = connectionContext.getNodeLayout().resolveStoredIds(ids, connectionContext.getConnection());
for (PatternSolution sol : executeQuery.getSelectResults()) {
for (int i = 0; i < sol.size(); i++) {
if (sol.getBinding(i) instanceof IRdbValue && !((IRdbValue) sol.getBinding(i)).populated()) {
((IRdbValue) sol.getBinding(i)).setValue(values.get((((IRdbValue) sol.getBinding(i)).getId())));
}
if (sol.getValue(i) instanceof IRdbValue && !((IRdbValue) sol.getValue(i)).populated()) {
((IRdbValue) sol.getValue(i)).setValue(values.get((((IRdbValue) sol.getValue(i)).getId())));
}
}
}
} else if (executeQuery.isConstructResult() || executeQuery.isDescribeResult()) {
HashSet<Long> ids = new HashSet<Long>();
for (org.openanzo.rdf.Statement stmt : executeQuery.getConstructResults()) {
if (stmt.getSubject() instanceof IRdbValue && !((IRdbValue) stmt.getSubject()).populated()) {
ids.add(((IRdbValue) stmt.getSubject()).getId());
}
if (stmt.getPredicate() instanceof IRdbValue && !((IRdbValue) stmt.getPredicate()).populated()) {
ids.add(((IRdbValue) stmt.getPredicate()).getId());
}
if (stmt.getObject() instanceof IRdbValue && !((IRdbValue) stmt.getObject()).populated()) {
ids.add(((IRdbValue) stmt.getObject()).getId());
}
if (stmt.getNamedGraphUri() instanceof IRdbValue && !((IRdbValue) stmt.getNamedGraphUri()).populated()) {
ids.add(((IRdbValue) stmt.getNamedGraphUri()).getId());
}
}
Map<Long, Value> values = connectionContext.getNodeLayout().resolveStoredIds(ids, connectionContext.getConnection());
for (org.openanzo.rdf.Statement stmt : executeQuery.getConstructResults()) {
if (stmt.getSubject() instanceof IRdbValue && !((IRdbValue) stmt.getSubject()).populated()) {
((IRdbValue) stmt.getSubject()).setValue(values.get((((IRdbValue) stmt.getSubject()).getId())));
}
if (stmt.getPredicate() instanceof IRdbValue && !((IRdbValue) stmt.getPredicate()).populated()) {
((IRdbValue) stmt.getPredicate()).setValue(values.get((((IRdbValue) stmt.getPredicate()).getId())));
}
if (stmt.getObject() instanceof IRdbValue && !((IRdbValue) stmt.getObject()).populated()) {
((IRdbValue) stmt.getObject()).setValue(values.get((((IRdbValue) stmt.getObject()).getId())));
}
if (stmt.getNamedGraphUri() instanceof IRdbValue && !((IRdbValue) stmt.getNamedGraphUri()).populated()) {
((IRdbValue) stmt.getNamedGraphUri()).setValue(values.get((((IRdbValue) stmt.getNamedGraphUri()).getId())));
}
}
}
if (RequestAnalysis.isAnalysisEnabled()) {
long end = System.currentTimeMillis();
RequestAnalysis.addAnalysisProperty("queryPopulateResultNodes", (end - start));
}
return executeQuery;
} finally {
if (isMine)
datasource.commit(connectionContext.getConnection(), false, false);
}
} catch (ParseException e) {
throw new GlitterParseException(e, query, e.getMessage());
}
} catch (RdbException e) {
log.error(LogUtils.RDB_MARKER, "Error executing sql query for sparql", e);
throw new AnzoException(ExceptionConstants.SERVER.QUERY_FAILED_ERROR, e, query);
} finally {
if (connectionContext != null) {
datasource.returnQueryContext(connectionContext);
}
}
}
private void populateRdbNode(Object node, Connection connection) throws RdbException {
if (node instanceof IRdbValue) {
IRdbValue value = (IRdbValue) node;
value.populate(connection);
}
}
public boolean cancel(IOperationContext context, String operationId) throws AnzoException {
return engine.cancelQuery(operationId);
}
public void cancel(IOperationContext context, String operationId, Writer output) throws AnzoException {
boolean ok = cancel(context, operationId);
try {
output.write(Boolean.toString(ok));
} catch (IOException ioe) {
throw new AnzoException(ExceptionConstants.IO.WRITE_ERROR, ioe);
}
}
}