/*******************************************************************************
* 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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.openanzo.exceptions.AnzoException;
import org.openanzo.rdf.INamedGraph;
import org.openanzo.rdf.Resource;
import org.openanzo.rdf.Statement;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Value;
import org.openanzo.services.IPrecondition;
/**
* Manages both current transactions as they are built up on a per-thread basis and committed transactions that have not yet been replicated up to the
* repository.
*
* This class is thread safe. The active transactions as isolated per-thread, see ThreadLocal for details.
*
* @author Joe Betz <jpbetz@cambridgesemantics.com>
*
*/
public class TransactionQueue {
// Array of committed transactions.
protected final List<Transaction> committedTransactions;
// map of threads to IsolatedTransactions
protected final ThreadLocal<IsolatedTransaction> isolatedTransactions;
protected final TransactionPersistence transactionPersistence;
/**
* Provides per-thread transaction isolation.
*
* @author Joe Betz <jpbetz@cambridgesemantics.com>
*
*/
protected static class IsolatedTransaction {
// The current transaction.
public Transaction currentTransaction;
// The root of the current transaction (root of the transaction tree).
public Transaction currentTransactionRoot;
}
/**
* Initialize local data structures.
*/
TransactionQueue() {
this.committedTransactions = new CopyOnWriteArrayList<Transaction>();
this.isolatedTransactions = new ThreadLocal<IsolatedTransaction>();
this.transactionPersistence = null;
}
public TransactionQueue(TransactionPersistence transactionPersistence) {
this.committedTransactions = transactionPersistence.loadTransactions();
this.isolatedTransactions = new ThreadLocal<IsolatedTransaction>();
this.transactionPersistence = transactionPersistence;
}
List<Transaction> getCommittedTransactions() {
synchronized (committedTransactions) {
List<Transaction> committedTransactions = new ArrayList<Transaction>(this.committedTransactions);
//this.committedTransactions.clear();
return committedTransactions;
}
}
protected IsolatedTransaction getIsolatedTransaction() {
return isolatedTransactions.get();
}
protected IsolatedTransaction getOrCreateIsolatedTransaction() {
IsolatedTransaction isolatedTransaction = isolatedTransactions.get();
if (isolatedTransaction != null)
return isolatedTransaction;
isolatedTransaction = new IsolatedTransaction();
isolatedTransactions.set(isolatedTransaction);
return isolatedTransaction;
}
protected void removeIsolatedTransaction() {
isolatedTransactions.remove();
}
void removeCommittedTransaction(Transaction transaction) {
this.committedTransactions.remove(transaction);
if (transactionPersistence != null) {
transactionPersistence.removeTransaction(transaction);
}
}
/**
* Checks if in transaction.
*
* @return True if in a transaction, false otherwise.
*/
boolean inTransaction() {
IsolatedTransaction isolatedTransaction = getIsolatedTransaction();
return isolatedTransaction != null && isolatedTransaction.currentTransaction != null;
}
/**
* Clears the queue, removing all stored transactions.
*/
void clear() {
this.committedTransactions.clear();
if (this.transactionPersistence != null) {
this.transactionPersistence.clear();
}
removeIsolatedTransaction();
}
/**
* create a new Transaction, if currentTransaction is not null, set it to the child of the curentTransaction, and set the currentTransaction to point to the
* new Transaction, and set parentTransaction pointer of new transaction
*
* @param preconditions
* The precondition(s) associated with this transaction that must be valid for this transaction to succeed.
*/
void begin(Set<IPrecondition> preconditions) {
IsolatedTransaction isolatedTransaction = getOrCreateIsolatedTransaction();
Transaction newTransaction = new Transaction(preconditions);
if (isolatedTransaction.currentTransaction == null) {
isolatedTransaction.currentTransaction = newTransaction;
isolatedTransaction.currentTransactionRoot = isolatedTransaction.currentTransaction;
} else {
isolatedTransaction.currentTransaction.childTransaction = newTransaction;
newTransaction.parentTransaction = isolatedTransaction.currentTransaction;
isolatedTransaction.currentTransaction = newTransaction;
}
}
void begin(IPrecondition precondition) {
Set<IPrecondition> preconditions = new HashSet<IPrecondition>();
preconditions.add(precondition);
begin(preconditions);
}
/**
* create a new transaction with no preconditions.
*/
void begin() {
begin(new HashSet<IPrecondition>());
}
/**
* if currentTransaction.parentTransaction and currentTransaction.previousTransaction are 'both' null, move the currentTransaction to the
* committedTransactions list, and set currentTransaction to null. Otherwise, set currentTransaction to the parent of the previous-most sibling of
* currentTransaction. If a parent is found, create a sibling for the parent. Set the next pointer of the sibling to the parent, and the prev pointer of the
* parent to the sibling. Finally set the currentTransaction to point to the new sibling. If a parent is not found, that means we have committed a sibling
* of the top-level transaction, which means we are committing the top-level transaction itself, so queue the transaction.
*
* @return return whether or not a full transaction tree has been committed.
*
* @throws AnzoException
* Throws error if no matching begin call has already been made within the same thread context.
*/
boolean commit() {
IsolatedTransaction isolatedTransaction = getOrCreateIsolatedTransaction();
if (isolatedTransaction.currentTransaction == null) {
return false;
}
boolean transactionComplete = false;
if (isolatedTransaction.currentTransaction.parentTransaction == null && isolatedTransaction.currentTransaction.previousTransaction == null) {
if (!isolatedTransaction.currentTransaction.isEmpty()) {
synchronized (committedTransactions) {
this.committedTransactions.add(isolatedTransaction.currentTransaction);
}
if (this.transactionPersistence != null) {
this.transactionPersistence.saveTransaction(isolatedTransaction.currentTransaction);
}
}
transactionComplete = true;
removeIsolatedTransaction();
} else {
// find the parent. We walk back the prev-trans chain until it ends.
while (isolatedTransaction.currentTransaction.previousTransaction != null) {
isolatedTransaction.currentTransaction = isolatedTransaction.currentTransaction.previousTransaction;
}
if (isolatedTransaction.currentTransaction.parentTransaction != null) {
Transaction newSibling = new Transaction(Collections.<IPrecondition> emptySet());
newSibling.previousTransaction = isolatedTransaction.currentTransaction.parentTransaction;
isolatedTransaction.currentTransaction.parentTransaction.nextTransaction = newSibling;
isolatedTransaction.currentTransaction = newSibling;
} else {
if (!isolatedTransaction.currentTransaction.isEmpty()) {
synchronized (committedTransactions) {
this.committedTransactions.add(isolatedTransaction.currentTransaction);
}
if (this.transactionPersistence != null) {
this.transactionPersistence.saveTransaction(isolatedTransaction.currentTransaction);
}
}
transactionComplete = true;
removeIsolatedTransaction();
}
}
return transactionComplete;
}
/**
* simply set currentTransaction to the parent of the previous most sibling of currentTransaction, Note that the previous-most sibling may be us and so go
* directly to parentTransaction. If the previous most sibling has no parent, then we aborting a sibling of the top-level transaction, i.e. we are aborting
* the top-level transaction
*
* @throws AnzoException
* Throws error if no matching begin call has already been made within the same thread context.
*/
void abort() {
IsolatedTransaction isolatedTransaction = getOrCreateIsolatedTransaction();
if (isolatedTransaction.currentTransaction == null) {
return;
}
if (isolatedTransaction.currentTransaction.parentTransaction == null && isolatedTransaction.currentTransaction.previousTransaction == null) {
removeIsolatedTransaction();
} else {
// find the parent. We walk back the prev-trans chain until it ends.
while (isolatedTransaction.currentTransaction.previousTransaction != null) {
isolatedTransaction.currentTransaction = isolatedTransaction.currentTransaction.previousTransaction;
}
if (isolatedTransaction.currentTransaction.parentTransaction != null) {
isolatedTransaction.currentTransaction = isolatedTransaction.currentTransaction.parentTransaction;
} else {
removeIsolatedTransaction();
}
}
}
INamedGraph getTransactionContext() {
IsolatedTransaction isolatedTransaction = getOrCreateIsolatedTransaction();
if (isolatedTransaction.currentTransaction == null) {
return null;
}
return isolatedTransaction.currentTransaction.getContext();
}
/**
* Add a single statement or multiple statements to the currentTransaction.
*
* @param statements
* Statements added to the currentTransaction.
*/
void add(Statement... statements) {
IsolatedTransaction isolatedTransaction = getOrCreateIsolatedTransaction();
if (isolatedTransaction.currentTransaction == null) {
// it is the TransactionProxy's responsibility to make sure we have a transaction
throw new Error("Transaction queue not in transaction");
} else {
isolatedTransaction.currentTransaction.add(statements);
}
}
/**
* Remove a single statement or multiple statements from the currentTransaction
*
* @param statements
* Statements removed from the currentTransaction.
*/
void remove(Statement... statements) {
IsolatedTransaction isolatedTransaction = getOrCreateIsolatedTransaction();
if (isolatedTransaction.currentTransaction == null) {
// it is the TransactionProxy's responsibility to make sure we have a transaction
throw new Error("Transaction queue not in transaction");
} else {
isolatedTransaction.currentTransaction.remove(statements);
}
}
/**
* Filter the given Set of Statement on the additions and deletions in every Transaction. The quad pattern arguments act as a hint to narrow the filtering.
* In particular, every Statement in statements is guaranteed to conform to the given quad pattern.
*
* @param statements
* Set of statements that are to be filtered.
* @param subject
* Subject of the quad pattern used to filter the given statements.
* @param predicate
* Predicate of the quad pattern used to filter the given statements.
* @param object
* Object of the quad pattern used to filter the given statements.
* @param namedGraphUris
* Named graph uri of the quad pattern used to filter the given statements.
*/
void filter(final Collection<Statement> statements, final Resource subject, final URI predicate, final Value object, final URI... namedGraphUris) {
IsolatedTransaction isolatedTransaction = getOrCreateIsolatedTransaction();
Transaction.MapFunction mapFunction = new Transaction.MapFunction() {
public void call(Transaction transaction) {
transaction.filter(statements, subject, predicate, object, namedGraphUris);
}
};
for (Transaction transaction : committedTransactions) {
transaction.walkTransactionTree(mapFunction);
}
if (isolatedTransaction.currentTransaction != null) {
isolatedTransaction.currentTransactionRoot.walkTransactionTree(mapFunction);
}
}
}