org.openanzo.datasource.services.BaseQueryService.java Source code

Java tutorial

Introduction

Here is the source code for org.openanzo.datasource.services.BaseQueryService.java

Source

/*******************************************************************************
 * 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:  Nov 1, 2007
 * Revision:   $Id$
 *
 * Contributors:
 *     Cambridge Semantics Incorporated - initial API and implementation
 *******************************************************************************/
package org.openanzo.datasource.services;

import java.io.IOException;
import java.io.Writer;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.ArrayUtils;
import org.openanzo.analysis.RequestAnalysis;
import org.openanzo.cache.ICacheProvider;
import org.openanzo.datasource.IQueryService;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.exceptions.ExceptionConstants.IO;
import org.openanzo.glitter.Engine;
import org.openanzo.glitter.ParseOnlyEngineConfig;
import org.openanzo.glitter.dataset.DefaultQueryDataset;
import org.openanzo.glitter.dataset.QueryDataset;
import org.openanzo.glitter.exception.GlitterParseException;
import org.openanzo.glitter.exception.GlitterRuntimeException;
import org.openanzo.glitter.query.PatternSolution;
import org.openanzo.glitter.query.QueryController;
import org.openanzo.glitter.query.SolutionSet;
import org.openanzo.glitter.syntax.concrete.ParseException;
import org.openanzo.rdf.Statement;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Constants.GRAPHS;
import org.openanzo.rdf.Constants.OPTIONS;
import org.openanzo.rdf.utils.SerializationConstants;
import org.openanzo.rdf.utils.SerializationUtils;
import org.openanzo.rdf.utils.UriGenerator;
import org.openanzo.services.DynamicServiceStats;
import org.openanzo.services.IOperationContext;
import org.openanzo.services.IUpdateResultListener;
import org.openanzo.services.Privilege;
import org.openanzo.services.serialization.CommonSerializationUtils;
import org.openanzo.services.serialization.IQueryResultsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base implementation of the IQueryService
 * 
 * @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>)
 * 
 */
public abstract class BaseQueryService extends BaseDatasourceComponent implements IQueryService {
    private static final Logger log = LoggerFactory.getLogger(BaseQueryService.class);

    private final QueryServiceWithCacheStats stats = new QueryServiceWithCacheStats("query", "askQuery");

    protected IQueryCache cache;

    protected void initCache(ICacheProvider cacheProvider) {
        if (cacheProvider != null) {
            createQueryCache(cacheProvider);
        }
    }

    protected void createQueryCache(ICacheProvider cacheProvider) {
        this.cache = new QueryCache(getDatasource().getInstanceURI().toString(), cacheProvider);
    }

    public String getName() {
        return getDatasource().getName() + ",Service=QueryService";
    }

    public String getDescription() {
        return "Query Service for " + getDatasource().getName();
    }

    public DynamicServiceStats getStatistics() {
        return stats;
    }

    public void start() throws AnzoException {
        stats.setEnabled(true);
    }

    public void reset() throws AnzoException {
        stats.reset();
        if (cache != null)
            cache.reset();
    }

    /**
     * Get the cacheUpdateListener for this service
     * 
     * @return the cacheUpdateListener for this service
     */
    public IUpdateResultListener getCacheUpdateListener() {
        return cache;
    }

    /**
     * Query where results are passed to handler
     * 
     * @param context
     * @param defaultNamedGraphs
     * @param graphs
     * @param namedDatasets
     * @param paramQuery
     * @param queryString
     * @param baseUri
     * @param queryResultsHandler
     * @throws AnzoException
     */
    public void query(IOperationContext context, Set<URI> defaultNamedGraphs, Set<URI> graphs,
            Set<URI> namedDatasets, String paramQuery, String queryString, URI baseUri,
            IQueryResultsHandler queryResultsHandler) throws AnzoException {
        // try {
        org.openanzo.glitter.query.QueryResults queryResults = query(context, defaultNamedGraphs, graphs,
                namedDatasets, paramQuery, queryString, baseUri);

        queryResultsHandler.start(queryResults.getQueryType(), queryResults.getTotalSolutions());
        switch (queryResults.getQueryType()) {
        case ASK:
            queryResultsHandler.handleAskResult(queryResults.getAskResults());
            break;
        case CONSTRUCT_QUADS:
        case DESCRIBE_QUADS:
        case CONSTRUCT:
        case DESCRIBE:
            for (Statement stmt : queryResults.getConstructResults()) {
                queryResultsHandler.handleStatement(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(),
                        stmt.getNamedGraphUri());
            }
            break;
        case SELECT:
            SolutionSet solutionSet = queryResults.getSelectResults();
            queryResultsHandler.handleBindings(solutionSet.getBindings());
            for (PatternSolution sol : solutionSet) {
                queryResultsHandler.handleSolution(sol);
            }
            break;
        }
        queryResultsHandler.end();
    }

    protected boolean canRead(IOperationContext context, URI namedGraphUri, boolean failIfNotFound)
            throws AnzoException {
        try {
            Set<URI> roles = getDatasource().getAuthorizationService().getRolesForGraph(context, namedGraphUri,
                    Privilege.READ);
            if (!context.getOperationPrincipal().isSysadmin() && !org.openanzo.rdf.utils.Collections.memberOf(roles,
                    context.getOperationPrincipal().getRoles())) {
                return false;
            }
            return true;
        } catch (AnzoException ae) {
            if (!failIfNotFound && ae.getErrorCode() == ExceptionConstants.DATASOURCE.NAMEDGRAPH.NOT_FOUND) {
                return false;
            }
            throw new GlitterRuntimeException(ae);
        }
    }

    protected QueryDataset resolveQueryUriSet(IOperationContext context, Set<URI> defaultGraphs,
            Set<URI> namedGraphs, Set<URI> namedDatasets) throws AnzoException {
        long start = System.currentTimeMillis();
        DefaultQueryDataset uriSet = new DefaultQueryDataset();
        if (defaultGraphs != null) {
            for (URI uri : defaultGraphs) {
                if (!uri.equals(GRAPHS.ALL_GRAPHS) && !uri.equals(GRAPHS.ALL_METADATAGRAPHS)
                        && !uri.equals(GRAPHS.ALL_NAMEDGRAPHS)) {
                    if (!canRead(context, uri, true)) {
                        new GlitterRuntimeException(ExceptionConstants.DATASOURCE.NO_READ_ERROR, uri.toString());
                    }
                } else if (uri.equals(GRAPHS.ALL_GRAPHS)) {
                    uriSet.defaultAllGraphs = true;
                } else if (uri.equals(GRAPHS.ALL_METADATAGRAPHS)) {
                    uriSet.defaultAllMetadataGraphs = true;
                } else if (uri.equals(GRAPHS.ALL_NAMEDGRAPHS)) {
                    uriSet.defaultAllNamedGraphs = true;
                }
            }
        }
        if (namedGraphs != null) {
            for (URI uri : namedGraphs) {
                if (!uri.equals(GRAPHS.ALL_GRAPHS) && !uri.equals(GRAPHS.ALL_METADATAGRAPHS)
                        && !uri.equals(GRAPHS.ALL_NAMEDGRAPHS)) {
                    if (!canRead(context, uri, true)) {
                        new GlitterRuntimeException(ExceptionConstants.DATASOURCE.NO_READ_ERROR, uri.toString());
                    }
                } else if (uri.equals(GRAPHS.ALL_GRAPHS)) {
                    uriSet.allGraphs = true;
                } else if (uri.equals(GRAPHS.ALL_METADATAGRAPHS)) {
                    uriSet.allMetadataGraphs = true;
                } else if (uri.equals(GRAPHS.ALL_NAMEDGRAPHS)) {
                    uriSet.allNamedGraphs = true;
                }
            }
        }
        boolean both = (((namedGraphs != null && namedGraphs.size() > 0) || uriSet.allGraphs
                || uriSet.allMetadataGraphs || uriSet.allNamedGraphs)
                || ((defaultGraphs != null && defaultGraphs.size() > 0) || uriSet.defaultAllGraphs
                        || uriSet.defaultAllMetadataGraphs || uriSet.defaultAllNamedGraphs))
                && (namedDatasets != null && namedDatasets.size() > 0);
        uriSet.setDefaultGraphs((both) ? new TreeSet<URI>() : new LinkedHashSet<URI>());
        uriSet.setNamedGraphs((both) ? new TreeSet<URI>() : new LinkedHashSet<URI>());

        // expand out the graph names from the named datasets
        if (defaultGraphs != null && defaultGraphs.size() > 0) {
            uriSet.getDefaultGraphURIs().addAll((both) ? defaultGraphs : new TreeSet<URI>(defaultGraphs));
        }
        if (namedGraphs != null && namedGraphs.size() > 0) {
            uriSet.getNamedGraphURIs().addAll((both) ? namedGraphs : new TreeSet<URI>(namedGraphs));
        }

        if (namedDatasets != null) {

            Boolean includeMetadata = context.getAttribute(OPTIONS.INCLUDEMETADATAGRAPHS, Boolean.class);
            boolean icm = includeMetadata != null ? includeMetadata : false;
            for (URI namedDataset : namedDatasets) {
                QueryDataset uris = getDatasource().getModelService().resolveNamedDataset(context, namedDataset);
                if (uris.fullyExpandedDatasets) {
                    uriSet.getDefaultGraphURIs().addAll(uris.getDefaultGraphURIs());
                    uriSet.getNamedGraphURIs().addAll(uris.getNamedGraphURIs());
                } else {
                    if (uris.getDefaultGraphURIs() != null) {
                        for (URI uri : uris.getDefaultGraphURIs()) {
                            if (context.getOperationPrincipal().isSysadmin() || canRead(context, uri, false)) {
                                uriSet.getDefaultGraphURIs().add(uri);
                                if (icm && !UriGenerator.isMetadataGraphUri(uri)
                                        && (context.getOperationPrincipal().isSysadmin() || canRead(context,
                                                UriGenerator.generateMetadataGraphUri(uri), false))) {
                                    uriSet.getDefaultGraphURIs().add(UriGenerator.generateMetadataGraphUri(uri));
                                }
                            }
                        }
                    }
                    if (uris.getNamedGraphURIs() != null) {
                        for (URI uri : uris.getNamedGraphURIs()) {
                            if (context.getOperationPrincipal().isSysadmin() || canRead(context, uri, false)) {
                                uriSet.getNamedGraphURIs().add(uri);
                                if (icm && !UriGenerator.isMetadataGraphUri(uri)
                                        && (context.getOperationPrincipal().isSysadmin() || canRead(context,
                                                UriGenerator.generateMetadataGraphUri(uri), false))) {
                                    uriSet.getNamedGraphURIs().add(UriGenerator.generateMetadataGraphUri(uri));
                                }
                            }
                        }
                    }
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug(LogUtils.TIMING_MARKER, "RESOLVE QUERY URIS,{},{}", (System.currentTimeMillis() - start),
                    uriSet.namedGraphs.size() + uriSet.defaultGraphs.size());
        }
        return uriSet;
    }

    protected QueryDataset resolveAndVerifyGraphs(IOperationContext context, Set<URI> dngs, Set<URI> ngs,
            Set<URI> namedDatasets, String queryString, URI baseUri, QueryController prepareQuery)
            throws AnzoException {
        QueryDataset uriSet = null;
        if ((dngs == null || dngs.size() == 0) && (ngs == null || ngs.size() == 0)
                && (namedDatasets == null || namedDatasets.size() == 0)) {
            uriSet = resolveQueryUriSet(context, prepareQuery.getQueryDataset().getDefaultGraphURIs(),
                    prepareQuery.getQueryDataset().getNamedGraphURIs(),
                    prepareQuery.getQueryDataset().getNamedDatasetURIs());
        } else {
            uriSet = resolveQueryUriSet(context, dngs, ngs, namedDatasets);
        }
        if (!(uriSet.allGraphs || uriSet.allMetadataGraphs || uriSet.allNamedGraphs || uriSet.defaultAllGraphs
                || uriSet.defaultAllMetadataGraphs || uriSet.defaultAllNamedGraphs)) {
            context.setAttribute("datasetResolved", Boolean.TRUE);
            uriSet.datasetFullyResolved = true;
        }
        return uriSet;
    }

    public org.openanzo.glitter.query.QueryResults query(IOperationContext context, Set<URI> defaultNamedGraphs,
            Set<URI> graphs, Set<URI> namedDatasets, String paramQuery, String queryString, URI baseUri)
            throws AnzoException {
        if (queryString == null || queryString.length() == 0)
            queryString = paramQuery; // TEMP fix until we drop support for
        // paramQuery in messages
        long start = 0;
        if (stats.isEnabled() || log.isDebugEnabled()) {
            start = System.currentTimeMillis();
        }
        if (getLockProvider() != null)
            getLockProvider().readLock().lock();
        logEntry();
        if (log.isDebugEnabled()) {
            log.debug(LogUtils.DATASOURCE_MARKER, "Query:{} [{}] [{}] [{}]", new Object[] { queryString,
                    defaultNamedGraphs != null ? ArrayUtils.toString(defaultNamedGraphs.toArray(new URI[0]))
                            : "null",
                    graphs != null ? ArrayUtils.toString(graphs.toArray(new URI[0])) : "null",
                    namedDatasets != null ? ArrayUtils.toString(namedDatasets.toArray(new URI[0])) : "null" });
        }
        try {
            QueryController prepareQuery = new Engine(new ParseOnlyEngineConfig()).prepareQuery(null, queryString,
                    null, baseUri);
            QueryDataset uriSet = resolveAndVerifyGraphs(context, defaultNamedGraphs, graphs, namedDatasets,
                    queryString, baseUri, prepareQuery);
            Boolean skipCache = context.getAttribute(OPTIONS.SKIPCACHE, Boolean.class);
            boolean skip = (skipCache != null && skipCache) || cache == null;
            skip |= (uriSet.allGraphs || uriSet.allMetadataGraphs || uriSet.allNamedGraphs
                    || uriSet.defaultAllGraphs || uriSet.defaultAllMetadataGraphs || uriSet.defaultAllNamedGraphs);
            String cacheString = !skip ? queryString + uriSet.getCacheString() : null;

            org.openanzo.glitter.query.QueryResults queryResults = !skip ? cache.findResults(cacheString, uriSet)
                    : null;
            if (queryResults == null) {
                if (stats.isEnabled()) {
                    stats.getCacheMiss().increment();
                }
                queryResults = executeQueryInternal(context, uriSet, queryString, baseUri);
                if (!skip && cache != null) {
                    cache.cacheResults(cacheString, queryResults, uriSet);
                }
                if (RequestAnalysis.isAnalysisEnabled(context.getAttributes())) {
                    RequestAnalysis.addAnalysisProperty(RequestAnalysis.ANS_PROP_CACHE_HIT, Boolean.FALSE);
                }
            } else {
                if (stats.isEnabled()) {
                    stats.getCacheHit().increment();
                }
                if (RequestAnalysis.isAnalysisEnabled(context.getAttributes())) {
                    RequestAnalysis.addAnalysisProperty(RequestAnalysis.ANS_PROP_CACHE_HIT, Boolean.TRUE);
                }
            }
            if (log.isDebugEnabled()) {
                log.debug(LogUtils.TIMING_MARKER, "Total Query Time:results,{},{},{}", new Object[] {
                        (System.currentTimeMillis() - start), queryResults.getTotalSolutions(), queryString });
            }
            return queryResults;
        } catch (ParseException pe) {
            throw new GlitterParseException(pe, queryString, pe.getMessage());
        } finally {
            if (stats.isEnabled()) {
                stats.use("query", (System.currentTimeMillis() - start));
            }

            if (getLockProvider() != null)
                getLockProvider().readLock().unlock();
            logExit();
        }
    }

    public void askQuery(IOperationContext context, Set<URI> defaultNamedGraphs, Set<URI> graphs,
            Set<URI> namedDatasets, String paramQuery, String queryString, URI baseUri, boolean current, Writer out)
            throws AnzoException {
        boolean queryResults = askQuery(context, defaultNamedGraphs, graphs, namedDatasets, paramQuery, queryString,
                baseUri, current);
        SerializationUtils.writeValue(queryResults, out, null);
    }

    public boolean askQuery(IOperationContext context, Set<URI> defaultNamedGraphs, Set<URI> graphs,
            Set<URI> namedDatasets, String paramQuery, String queryString, URI baseUri, boolean current)
            throws AnzoException {
        if (queryString == null || queryString.length() == 0)
            queryString = paramQuery; // TEMP fix until we drop support for
        // paramQuery in messages
        long start = 0;
        if (stats.isEnabled()) {
            start = System.currentTimeMillis();
        }
        if (getLockProvider() != null)
            getLockProvider().readLock().lock();
        logEntry();
        try {
            QueryController prepareQuery = new Engine(new ParseOnlyEngineConfig()).prepareQuery(null, queryString,
                    null, baseUri);
            QueryDataset uriSet = resolveAndVerifyGraphs(context, defaultNamedGraphs, graphs, namedDatasets,
                    queryString, baseUri, prepareQuery);
            Boolean skipCache = context.getAttribute(OPTIONS.SKIPCACHE, Boolean.class);
            boolean skip = (skipCache != null && skipCache) || cache == null;
            skip |= (uriSet.allGraphs || uriSet.allMetadataGraphs || uriSet.allNamedGraphs
                    || uriSet.defaultAllGraphs || uriSet.defaultAllMetadataGraphs || uriSet.defaultAllNamedGraphs);
            String cacheString = !skip ? queryString + uriSet.getCacheString() : null;

            org.openanzo.glitter.query.QueryResults queryResults = !skip ? cache.findResults(cacheString, uriSet)
                    : null;

            if (queryResults == null) {
                if (stats.isEnabled()) {
                    stats.getCacheMiss().increment();
                }
                queryResults = executeQueryInternal(context, uriSet, queryString, baseUri);
                if (!skip && cache != null) {
                    cache.cacheResults(cacheString, queryResults, uriSet);
                }
                if (RequestAnalysis.isAnalysisEnabled(context.getAttributes())) {
                    RequestAnalysis.addAnalysisProperty(RequestAnalysis.ANS_PROP_CACHE_HIT, Boolean.FALSE);
                }
            } else {
                if (stats.isEnabled()) {
                    stats.getCacheHit().increment();
                }
                if (RequestAnalysis.isAnalysisEnabled(context.getAttributes())) {
                    RequestAnalysis.addAnalysisProperty(RequestAnalysis.ANS_PROP_CACHE_HIT, Boolean.TRUE);
                }
            }
            return queryResults.getAskResults();
        } catch (ParseException pe) {
            throw new GlitterParseException(pe, queryString, pe.getMessage());
        } finally {
            if (stats.isEnabled()) {
                stats.use("askQuery", (System.currentTimeMillis() - start));
            }
            if (getLockProvider() != null)
                getLockProvider().readLock().unlock();
            logExit();
        }
    }

    /**
     * Run a query on the server's data
     * 
     * @param context
     *            {@link IOperationContext} context for this operation
     * 
     * @param queryController
     *            prepopulated QueryController
     * @return the results of the query
     * @throws AnzoException
     */
    protected abstract org.openanzo.glitter.query.QueryResults executeQueryInternal(IOperationContext context,
            QueryDataset uriSet, String queryString, URI baseUri) throws AnzoException;

    public void query(IOperationContext context, Set<URI> defaultNamedGraphs, Set<URI> graphs,
            Set<URI> namedDatasets, String paramQuery, String queryString, URI baseUri, Writer out, String format)
            throws AnzoException {
        try {
            org.openanzo.glitter.query.QueryResults queryResults = query(context, defaultNamedGraphs, graphs,
                    namedDatasets, paramQuery, queryString, baseUri);
            out.write(queryResults.getQueryType().name() + "\n");
            CommonSerializationUtils.writeQueryResults(queryResults, out, format);
            context.setAttribute(SerializationConstants.totalSolutions, queryResults.getTotalSolutions());
        } catch (IOException ioe) {
            throw new AnzoException(IO.WRITE_ERROR, ioe, ioe.getMessage());
        }
    }
}