/* * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007. * * Licensed under the Aduna BSD-style license. */ package org.openrdf.sail.helpers; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.aduna.concurrent.locks.ExclusiveLockManager; import info.aduna.concurrent.locks.Lock; import info.aduna.concurrent.locks.ReadWriteLockManager; import info.aduna.concurrent.locks.WritePrefReadWriteLockManager; import info.aduna.iteration.CloseableIteration; import org.openrdf.model.Namespace; 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.Dataset; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.algebra.TupleExpr; import org.openrdf.sail.SailConnection; import org.openrdf.sail.SailConnectionListener; import org.openrdf.sail.SailException; /** * Abstract Class offering base functionality for SailConnection * implementations. * * @author Arjohn Kampman * @author jeen */ public abstract class SailConnectionBase implements SailConnection { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); /*-----------* * Variables * *-----------*/ private final SailBase sailBase; private boolean isOpen; private boolean txnActive; /** * A read-write lock manager used to handle multi-threaded access on the * connection. Every operation on the connection must first obtain a shared * (read) lock. When close() is invoked on this connection, the close() * method will first obtain an exclusive (write) lock: it will wait until * active operations finish and then block any further operations on the * connection. */ private final ReadWriteLockManager connectionLockManager = new WritePrefReadWriteLockManager(); /** * A multi-read-single-write lock manager used to handle multi-threaded * access on the transaction-related methods of a connection. Every * transaction operation except commit and rollback must first obtain a * shared (read) lock. The commit and rollback themselves will first obtain * an exclusive (write) lock, which will guarantee that there will be no * updates during these operations. */ private final ExclusiveLockManager txnLockManager = new ExclusiveLockManager(); // FIXME: use weak references here? private List<SailBaseIteration> activeIterations = Collections.synchronizedList(new LinkedList<SailBaseIteration>()); private List<SailConnectionListener> listeners; /*--------------* * Constructors * *--------------*/ public SailConnectionBase(SailBase sailBase) { this.sailBase = sailBase; isOpen = true; txnActive = false; listeners = new ArrayList<SailConnectionListener>(0); } /*---------* * Methods * *---------*/ public final boolean isOpen() throws SailException { return isOpen; } protected void verifyIsOpen() throws SailException { if (!isOpen) { throw new IllegalStateException("Connection has been closed"); } } public final void close() throws SailException { // obtain an exclusive lock so that any further operations on this // connection (including those from any concurrent threads) are blocked. Lock conLock = getExclusiveConnectionLock(); try { if (isOpen) { try { while (true) { SailBaseIteration ci = null; synchronized (activeIterations) { if (activeIterations.isEmpty()) { break; } else { ci = activeIterations.remove(0); } } try { ci.forceClose(); } catch (SailException e) { throw e; } catch (Exception e) { throw new SailException(e); } } assert activeIterations.isEmpty(); if (txnActive) { logger.warn("Rolling back transaction due to connection close", new Throwable()); try { // Use internal method to avoid deadlock: the public // rollback method will try to obtain a connection lock rollbackInternal(); } finally { txnActive = false; } } closeInternal(); } finally { isOpen = false; sailBase.connectionClosed(this); } } } finally { // Release the exclusive lock. Any threads waiting to obtain a // non-exclusive read lock will get one and then fail with an // IllegalStateException, because the connection is no longer open. conLock.release(); } } public final CloseableIteration<? extends BindingSet, QueryEvaluationException> evaluate( TupleExpr tupleExpr, Dataset dataset, BindingSet bindings, boolean includeInferred) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); return registerIteration(evaluateInternal(tupleExpr, dataset, bindings, includeInferred)); } finally { conLock.release(); } } public final CloseableIteration<? extends Resource, SailException> getContextIDs() throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); return registerIteration(getContextIDsInternal()); } finally { conLock.release(); } } public final CloseableIteration<? extends Statement, SailException> getStatements(Resource subj, URI pred, Value obj, boolean includeInferred, Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); return registerIteration(getStatementsInternal(subj, pred, obj, includeInferred, contexts)); } finally { conLock.release(); } } public final long size(Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); return sizeInternal(contexts); } finally { conLock.release(); } } protected final boolean transactionActive() { return txnActive; } protected void autoStartTransaction() throws SailException { if (!txnActive) { startTransactionInternal(); txnActive = true; } } public final void commit() throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { if (txnActive) { commitInternal(); txnActive = false; } } finally { txnLock.release(); } } finally { conLock.release(); } } public final void rollback() throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { if (txnActive) { try { rollbackInternal(); } finally { txnActive = false; } } } finally { txnLock.release(); } } finally { conLock.release(); } } public final void addStatement(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); addStatementInternal(subj, pred, obj, contexts); } finally { txnLock.release(); } } finally { conLock.release(); } } public final void removeStatements(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); removeStatementsInternal(subj, pred, obj, contexts); } finally { txnLock.release(); } } finally { conLock.release(); } } public final void clear(Resource... contexts) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); clearInternal(contexts); } finally { txnLock.release(); } } finally { conLock.release(); } } public final CloseableIteration<? extends Namespace, SailException> getNamespaces() throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); return registerIteration(getNamespacesInternal()); } finally { conLock.release(); } } public final String getNamespace(String prefix) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); return getNamespaceInternal(prefix); } finally { conLock.release(); } } public final void setNamespace(String prefix, String name) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); setNamespaceInternal(prefix, name); } finally { txnLock.release(); } } finally { conLock.release(); } } public final void removeNamespace(String prefix) throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); removeNamespaceInternal(prefix); } finally { txnLock.release(); } } finally { conLock.release(); } } public final void clearNamespaces() throws SailException { Lock conLock = getSharedConnectionLock(); try { verifyIsOpen(); Lock txnLock = getTransactionLock(); try { autoStartTransaction(); clearNamespacesInternal(); } finally { txnLock.release(); } } finally { conLock.release(); } } public void addConnectionListener(SailConnectionListener listener) { synchronized (listeners) { listeners.add(listener); } } public void removeConnectionListener(SailConnectionListener listener) { synchronized (listeners) { listeners.remove(listener); } } protected boolean hasConnectionListeners() { synchronized (listeners) { return !listeners.isEmpty(); } } protected void notifyStatementAdded(Statement st) { synchronized (listeners) { for (SailConnectionListener listener : listeners) { listener.statementAdded(st); } } } protected void notifyStatementRemoved(Statement st) { synchronized (listeners) { for (SailConnectionListener listener : listeners) { listener.statementRemoved(st); } } } protected Lock getSharedConnectionLock() throws SailException { try { return connectionLockManager.getReadLock(); } catch (InterruptedException e) { throw new SailException(e); } } protected Lock getExclusiveConnectionLock() throws SailException { try { return connectionLockManager.getWriteLock(); } catch (InterruptedException e) { throw new SailException(e); } } protected Lock getTransactionLock() throws SailException { try { return txnLockManager.getExclusiveLock(); } catch (InterruptedException e) { throw new SailException(e); } } /** * Registers an iteration as active by wrapping it in a * {@link SailBaseIteration} object and adding it to the list of active * iterations. */ protected <T, E extends Exception> CloseableIteration<T, E> registerIteration(CloseableIteration<T, E> iter) { SailBaseIteration<T, E> result = new SailBaseIteration<T, E>(iter, this); activeIterations.add(result); return result; } /** * Called by {@link SailBaseIteration} to indicate that it has been closed. */ protected void iterationClosed(SailBaseIteration iter) { activeIterations.remove(iter); } protected abstract void closeInternal() throws SailException; protected abstract CloseableIteration<? extends BindingSet, QueryEvaluationException> evaluateInternal( TupleExpr tupleExpr, Dataset dataset, BindingSet bindings, boolean includeInferred) throws SailException; protected abstract CloseableIteration<? extends Resource, SailException> getContextIDsInternal() throws SailException; protected abstract CloseableIteration<? extends Statement, SailException> getStatementsInternal( Resource subj, URI pred, Value obj, boolean includeInferred, Resource... contexts) throws SailException; protected abstract long sizeInternal(Resource... contexts) throws SailException; protected abstract void startTransactionInternal() throws SailException; protected abstract void commitInternal() throws SailException; protected abstract void rollbackInternal() throws SailException; protected abstract void addStatementInternal(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException; protected abstract void removeStatementsInternal(Resource subj, URI pred, Value obj, Resource... contexts) throws SailException; protected abstract void clearInternal(Resource... contexts) throws SailException; protected abstract CloseableIteration<? extends Namespace, SailException> getNamespacesInternal() throws SailException; protected abstract String getNamespaceInternal(String prefix) throws SailException; protected abstract void setNamespaceInternal(String prefix, String name) throws SailException; protected abstract void removeNamespaceInternal(String prefix) throws SailException; protected abstract void clearNamespacesInternal() throws SailException; }