/*******************************************************************************
* 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
*
* Contributors:
* Cambridge Semantics Incorporated
*******************************************************************************/
package org.openanzo.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.exceptions.ExceptionConstants;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.exceptions.Messages;
import org.openanzo.glitter.Engine;
import org.openanzo.glitter.dataset.DefaultQueryDataset;
import org.openanzo.glitter.query.QueryResults;
import org.openanzo.glitter.syntax.concrete.ParseException;
import org.openanzo.rdf.BaseQuadStore;
import org.openanzo.rdf.IQuadStore;
import org.openanzo.rdf.Resource;
import org.openanzo.rdf.Statement;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Value;
import org.openanzo.rdf.query.CoreEngineConfig;
import org.openanzo.rdf.query.QuadStoreEngineConfig;
import org.openanzo.rdf.utils.UriGenerator;
import org.openanzo.rdf.vocabulary.Anzo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TransactionProxy itself is fairly simple. The add method takes in either a Statement, list of Statement or s,p,o,c quad definition. First it checks if a
* transaction has already begun. If not, it wraps the entire add operation in a new transaction. It then asks the TransactionQueue to which it is bound for the
* current Transaction (possibly just created) and in turn invokes Transaction.addStatmentAddition. The find method first asks the wrapped IQuadStore to perform
* the same find, and then filters the results using the TransactionQueue.
*
* @author Joe Betz
*
*/
class TransactionProxy extends BaseQuadStore {
private static final Logger log = LoggerFactory.getLogger(TransactionProxy.class);
private final IQuadStore quadStore;
final TransactionQueue transactionQueue;
private final Engine glitter;
private AnzoClient anzoClient;
/**
* Initialization
*
* @param quadStore
* The store to which this store will proxy requests.
* @param transactionQueue
* The queue that holds transactions for this graph.
*/
TransactionProxy(IQuadStore quadStore, TransactionQueue transactionQueue, AnzoClient anzoClient) {
this.quadStore = quadStore;
this.transactionQueue = transactionQueue;
CoreEngineConfig config = new QuadStoreEngineConfig(this);
glitter = new Engine(config);
this.anzoClient = anzoClient;
}
/**
* Traps the additions in a transaction.
*
* @param statements
*/
public void add(Statement... statements) {
boolean inTransaction = this.transactionQueue.inTransaction();
if (!inTransaction) {
this.transactionQueue.begin();
}
this.transactionQueue.add(statements);
if (!inTransaction) {
this.transactionQueue.commit();
}
}
/**
* Traps the removes in a transaction.
*
* @param statements
*/
public void remove(Statement... statements) {
boolean inTransaction = this.transactionQueue.inTransaction();
if (!inTransaction) {
this.transactionQueue.begin();
}
this.transactionQueue.remove(statements);
if (!inTransaction) {
this.transactionQueue.commit();
}
}
/**
* Performs a find on the given pattern. Find is first called on the proxied store, then the statements are filtered out by the changes in the transaction
* queue.
*
* @param subject
* Subject value to match, or wildcard if null
* @param predicate
* Predicate value to match, or wildcard if null
* @param object
* Object value to match, or wildcard if null
* @param namedGraphUris
* uri used to match the named graph parameter of statements, or wildcard if null
* @return An array of matching statements.
*/
public Collection<Statement> find(Resource subject, URI predicate, Value object, URI... namedGraphUris) {
Collection<Statement> statements = new ArrayList<Statement>(this.quadStore.find(subject, predicate, object, namedGraphUris));
this.transactionQueue.filter(statements, subject, predicate, object, namedGraphUris);
return statements;
}
/**
* Removes all statements in this store.
*/
public void clear() {
remove(find(null, null, null, (URI[]) null));
}
/**
* Return if container is empty
*
* @return true if container is empty
*/
public boolean isEmpty() {
return this.size() == 0;
}
/**
* Checks if a named graph is empty.
*
* @param namedGraphUri
* The uri of the named graph to test.
* @return Returns true if the number of statements in the store for the given namedGraphUris is zero, false otherwise. If namedGraphUris is empty, returns
* true if the the total number of statements in this store is zero, false otherwise.
*/
public boolean isEmpty(URI namedGraphUri) {
return this.size(namedGraphUri) == 0;
}
/**
* Return number of statements in container
*
* @return number of statements in container
*/
public int size() {
return find(null, null, null, (URI[]) null).size();
}
/**
* Abstract method that must be implemented by subclasses.
*
* Returns the number of statements in the store for given namedGraphUris. If the given named graph uris are not specified, it returns the total number of
* statements in the store.
*
* @param namedGraphUris
* uri used to determine the total sizes of specified graphs.
* @return The number of statements in the store for the given namedGraphUris. If namedGraphUris is null, returns the total number of statements in this
* store.
*/
public int size(URI... namedGraphUris) {
return find(null, null, null, namedGraphUris).size();
}
/**
* Return the set of namedGraphUris contained within this store. The only way to correctly get all namedGraphUris, is to enumerate all the statement
* (filtered of course), and build up a set of namedGraphUris. This is because the baseContainer could contain a single statement from a named graph and the
* transaction queue could contain a deletion for that statement. perhaps we should disallow this method.
*
* @return An array of named graph uris contained in this store.
*/
public Collection<URI> getNamedGraphUris() {
Collection<Statement> stmts = getStatements();
Set<URI> namedGraphUris = new HashSet<URI>();
for (Statement stmt : stmts) {
namedGraphUris.add(stmt.getNamedGraphUri());
}
return namedGraphUris;
}
/**
* Return true if the container contains atleast 1 statement that matches the pattern of subj,prop,obj,namedGraphUri
*
* @param subj
* Subject resource to match, or wildcard if null
* @param prop
* Predicate uri to match, or wildcard if null
* @param obj
* Object value to match, or wildcard if null
* @param namedGraphUris
* named graph values to match, or wildcard if null
* @return true if the container contains atleast 1 statement that matches the pattern of subj,prop,obj
*/
public boolean contains(Resource subj, URI prop, Value obj, URI... namedGraphUris) {
return this.find(subj, prop, obj, namedGraphUris).size() > 0;
}
/**
* Return true if the container contains atleast 1 statement that matches the statement provided
*
* @param statement
* Statement to check for existence in container
* @return true if the container contains atleast 1 statement that matches the statement provided
*/
public boolean contains(Statement statement) {
URI[] resources;
if (statement.getNamedGraphUri() != null) {
resources = new URI[] { statement.getNamedGraphUri() };
} else {
resources = new URI[] {};
}
return contains(statement.getSubject(), statement.getPredicate(), statement.getObject(), resources);
}
/**
* Execute a SPARQL query against the data within this container
*
* @param defaultNamedGraphs
* Set<URI> of URIs for NamedGraphs that will make up the default graph for this query
* @param namedGraphs
* Set<URI> of URIs for NamedGraphs that will make up the NamedGraphs for this query
* @param namedDatasets
* Set of URIs for named datasets that will contribute to the query's RDF dataset
* @param query
* SPARQL query string
* @param baseUri
* Base URI against which relative URI references in the SPARQL query are resolved
* @return Results of running query
* @throws AnzoException
*/
public QueryResults executeQuery(Set<URI> defaultNamedGraphs, Set<URI> namedGraphs, Set<URI> namedDatasets, String query, URI baseUri) throws AnzoException {
try {
HashSet<URI> defaultNgs = new HashSet<URI>();
HashSet<URI> ngs = new HashSet<URI>();
if (namedDatasets != null) {
for (URI uri : namedDatasets) {
for (Statement s : find(uri, Anzo.DEFAULTNAMEDGRAPH, null, uri)) {
defaultNgs.add((URI) s.getObject());
ngs.add((URI) s.getObject());
}
for (Statement s : find(uri, Anzo.DEFAULTGRAPH, null, uri)) {
defaultNgs.add((URI) s.getObject());
}
for (Statement s : find(uri, Anzo.NAMEDGRAPH, null, uri)) {
ngs.add((URI) s.getObject());
}
}
}
if (defaultNamedGraphs != null) {
for (URI ng : defaultNamedGraphs) {
defaultNgs.add(ng);
}
}
if (namedGraphs != null) {
for (URI ng : namedGraphs) {
ngs.add(ng);
}
}
UriGenerator.handleSpecialGraphUris(defaultNgs, this);
UriGenerator.handleSpecialGraphUris(ngs, this);
Set<URI> toRemove = new HashSet<URI>();
for (URI ng : defaultNgs) {
if (anzoClient.runAsUser.get() != null && !anzoClient.canReadNamedGraph(ng)) {
toRemove.add(ng);
}
}
defaultNgs.removeAll(toRemove);
toRemove.clear();
for (URI ng : ngs) {
if (anzoClient.runAsUser.get() != null && !anzoClient.canReadNamedGraph(ng)) {
toRemove.add(ng);
}
}
ngs.removeAll(toRemove);
DefaultQueryDataset queryDataset = new DefaultQueryDataset(defaultNgs, ngs);
return glitter.executeQuery(null, query, queryDataset, baseUri);
} catch (ParseException e) {
log.error(LogUtils.INTERNAL_MARKER, Messages.formatString(ExceptionConstants.GLITTER.PARSE_EXCEPTION, query, ""), e);
throw new AnzoException(ExceptionConstants.CLIENT.ERROR_PARSING_QUERY, e, query);
}
}
/**
* Get an iterator over all statements within this container
*
* @return CloseableIterator of all statements within this container
*/
public Collection<Statement> getStatements() {
return find(null, null, null, (URI[]) null);
}
}