package eu.fbk.knowledgestore.triplestore;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;
import org.openrdf.query.QueryEvaluationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.aduna.iteration.CloseableIteration;
import eu.fbk.knowledgestore.data.Handler;
import eu.fbk.knowledgestore.internal.Util;
import eu.fbk.knowledgestore.runtime.Component;
import eu.fbk.knowledgestore.runtime.Synchronizer;
/**
* A {@code TripleStore} wrapper that synchronizes and enforces a proper access to a wrapped
* {@code TripleStore}.
* <p>
* This wrapper provides the following guarantees with respect to external access to the wrapped
* {@link TripleStore}:
* <ul>
* <li>transaction are started and committed according to the synchronization strategy enforced by
* a supplied {@link Synchronizer};</li>
* <li>at most one thread at a time can access the wrapped {@code TripleStore} and its
* {@code TripleTransaction}s, with the only exception of {@link TripleStore#close()} and
* {@link TripleTransaction#end(boolean)} which may be called concurrently with other active
* operations;</li>
* <li>access to the wrapped {@code TripleStore} and its {@code TripleTransaction} is enforced to
* occur strictly in adherence with the lifecycle defined for {@link Component}s (
* {@code IllegalStateException}s are returned to the caller otherwise);</li>
* <li>method {@link #reset()} of wrapped {@code TripleStore} is called with no transactions
* active (this implies waiting for completion of pending transactions);</li>
* <li>before a {@code TripleTransaction} is ended, all the iterations previously returned and
* still open to be forcedly closed;</li>
* <li>before the {@code TripleStore} is closed, pending {@code TripleTransaction} are forcedly
* ended with a rollback.</li>
* </ul>
* </p>
*/
public class SynchronizedTripleStore extends ForwardingTripleStore {
private static final Logger LOGGER = LoggerFactory.getLogger(SynchronizedTripleStore.class);
private static final int NEW = 0;
private static final int INITIALIZED = 1;
private static final int CLOSED = 2;
private final TripleStore delegate;
private final Synchronizer synchronizer;
private final List<TripleTransaction> transactions;
private final AtomicInteger state;
/**
* Creates a new instance for the wrapped {@code TripleStore} and the {@code Synchronizer}
* specification string supplied.
*
* @param delegate
* the wrapped {@code DataStore}
* @param synchronizerSpec
* the synchronizer specification string (see {@link Synchronizer})
*/
public SynchronizedTripleStore(final TripleStore delegate, final String synchronizerSpec) {
this(delegate, Synchronizer.create(synchronizerSpec));
}
/**
* Creates a new instance for the wrapped {@code TripleStore} and {@code Synchronizer}
* specified.
*
* @param delegate
* the wrapped {@code TripleStore}
* @param synchronizer
* the synchronizer responsible to regulate the access to the wrapped
* {@code TripleStore}
*/
public SynchronizedTripleStore(final TripleStore delegate, final Synchronizer synchronizer) {
this.delegate = Preconditions.checkNotNull(delegate);
this.synchronizer = Preconditions.checkNotNull(synchronizer);
this.transactions = Lists.newArrayList();
this.state = new AtomicInteger(NEW);
LOGGER.debug("{} configured, synchronizer={}", getClass().getSimpleName(), synchronizer);
}
@Override
protected TripleStore delegate() {
return this.delegate;
}
private void checkState(final int expected) {
final int state = this.state.get();
if (state != expected) {
throw new IllegalStateException("TripleStore "
+ (state == NEW ? "not initialized"
: state == INITIALIZED ? "already initialized" : "already closed"));
}
}
@Override
public synchronized void init() throws IOException {
checkState(NEW);
super.init();
this.state.set(INITIALIZED);
}
@Override
public TripleTransaction begin(final boolean readOnly) throws IOException {
checkState(INITIALIZED);
this.synchronizer.beginTransaction(readOnly);
TripleTransaction transaction = null;
try {
synchronized (this) {
checkState(INITIALIZED);
transaction = delegate().begin(readOnly);
if (Thread.interrupted()) {
transaction.end(false);
throw new IllegalStateException("Interrupted");
}
transaction = new SynchronizedTripleTransaction(transaction, readOnly);
synchronized (this.transactions) {
this.transactions.add(transaction);
}
}
} finally {
if (transaction == null) {
this.synchronizer.endTransaction(readOnly);
}
}
return transaction;
}
@Override
public void reset() throws IOException {
checkState(INITIALIZED);
this.synchronizer.beginExclusive();
try {
synchronized (this) {
checkState(INITIALIZED);
delegate().reset();
}
} finally {
this.synchronizer.endExclusive();
}
}
@Override
public void close() {
if (!this.state.compareAndSet(INITIALIZED, CLOSED)
&& !this.state.compareAndSet(NEW, CLOSED)) {
return;
}
List<TripleTransaction> transactionsToEnd;
synchronized (this.transactions) {
transactionsToEnd = Lists.newArrayList(this.transactions);
}
try {
for (final TripleTransaction transaction : transactionsToEnd) {
try {
LOGGER.warn("Forcing rollback of tx " + transaction
+ " due to closure of TripleStore");
transaction.end(false);
} catch (final Throwable ex) {
LOGGER.error("Exception caught while ending tx " + transaction
+ " (rollback assumed): " + ex.getMessage(), ex);
}
}
} finally {
super.close();
}
}
private final class SynchronizedTripleTransaction extends ForwardingTripleTransaction {
private final TripleTransaction delegate;
private final List<WeakReference<CloseableIteration<?, ?>>> iterations;
private final boolean readOnly;
private final AtomicBoolean ended;
SynchronizedTripleTransaction(final TripleTransaction delegate, final boolean readOnly) {
this.delegate = delegate;
this.iterations = Lists.newArrayList();
this.readOnly = readOnly;
this.ended = new AtomicBoolean(false);
}
@Override
protected TripleTransaction delegate() {
return this.delegate;
}
private <T extends CloseableIteration<?, ?>> T registerIteration(
@Nullable final T iteration) {
synchronized (this.iterations) {
if (iteration == null) {
return null;
} else if (this.ended.get() || Thread.interrupted()) {
Util.closeQuietly(iteration);
throw new IllegalStateException("Closed / interrupted");
} else {
final int size = this.iterations.size();
for (int i = size - 1; i >= 0; --i) {
if (this.iterations.get(i).get() == null) {
this.iterations.remove(i);
}
}
this.iterations.add(new WeakReference<CloseableIteration<?, ?>>(iteration));
}
}
return iteration;
}
private void closeIterations() {
synchronized (this.iterations) {
final int size = this.iterations.size();
for (int i = size - 1; i >= 0; --i) {
Util.closeQuietly(this.iterations.remove(i).get());
}
}
}
private void checkState() {
if (this.ended.get()) {
throw new IllegalStateException("DataTransaction already ended");
}
if (Thread.interrupted()) {
throw new IllegalStateException("Interrupted");
}
}
@Override
public synchronized CloseableIteration<? extends Statement, ? extends Exception> get(
@Nullable final Resource subject, @Nullable final URI predicate,
@Nullable final Value object, @Nullable final Resource context)
throws IOException, IllegalStateException {
checkState();
return registerIteration(super.get(subject, predicate, object, context));
}
@Override
public synchronized CloseableIteration<BindingSet, QueryEvaluationException> query(
final SelectQuery query, @Nullable final BindingSet bindings,
@Nullable final Long timeout) throws IOException, UnsupportedOperationException {
checkState();
return registerIteration(super.query(query, bindings, timeout));
}
@Override
public synchronized void infer(@Nullable final Handler<? super Statement> handler)
throws IOException, IllegalStateException {
checkState();
super.infer(handler);
}
@Override
public synchronized void add(final Iterable<? extends Statement> stream)
throws IOException, IllegalStateException {
checkState();
super.add(stream);
}
@Override
public synchronized void remove(final Iterable<? extends Statement> stream)
throws IOException, IllegalStateException {
checkState();
super.remove(stream);
}
@Override
public void end(final boolean commit) throws IOException {
if (!this.ended.compareAndSet(false, true)) {
return;
}
closeIterations();
SynchronizedTripleStore.this.synchronizer.beginCommit();
try {
super.end(commit);
} finally {
SynchronizedTripleStore.this.synchronizer.endCommit();
SynchronizedTripleStore.this.synchronizer.endTransaction(this.readOnly);
synchronized (SynchronizedTripleStore.this.transactions) {
SynchronizedTripleStore.this.transactions.remove(this);
}
}
}
}
}