org.eclipse.rdf4j.sail.helpers.AbstractSail Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rdf4j-sail-api Show documentation
Show all versions of rdf4j-sail-api Show documentation
RDF Storage And Inference Layer ("Sail") API.
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*******************************************************************************/
package org.eclipse.rdf4j.sail.helpers;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.rdf4j.IsolationLevel;
import org.eclipse.rdf4j.IsolationLevels;
import org.eclipse.rdf4j.sail.Sail;
import org.eclipse.rdf4j.sail.SailConnection;
import org.eclipse.rdf4j.sail.SailException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An abstract Sail implementation that takes care of common sail tasks, including proper closing of active
* connections and a grace period for active connections during shutdown of the store.
*
* @author Herko ter Horst
* @author jeen
* @author Arjohn Kampman
*/
public abstract class AbstractSail implements Sail {
/*-----------*
* Constants *
*-----------*/
/**
* Default connection timeout on shutdown: 20,000 milliseconds.
*/
protected final static long DEFAULT_CONNECTION_TIMEOUT = 20000L;
/**
* default transaction isolation level, set to {@link IsolationLevels#READ_COMMITTED }.
*/
private IsolationLevel defaultIsolationLevel = IsolationLevels.READ_COMMITTED;
/**
* list of supported isolation levels. By default set to include {@link IsolationLevels#READ_UNCOMMITTED}
* and {@link IsolationLevels#SERIALIZABLE}. Specific store implementations are expected to alter this
* list according to their specific capabilities.
*/
private List supportedIsolationLevels = new ArrayList();
/**
* default value for the Iteration item sync threshold
*/
protected static final long DEFAULT_ITERATION_SYNC_THRESHOLD = 0L;
// Note: the following variable and method are package protected so that they
// can be removed when open connections no longer block other connections and
// they can be closed silently (just like in JDBC).
static final String DEBUG_PROP = "org.eclipse.rdf4j.repository.debug";
protected static boolean debugEnabled() {
try {
String value = System.getProperty(DEBUG_PROP);
return value != null && !value.equals("false");
}
catch (SecurityException e) {
// Thrown when not allowed to read system properties, for example when
// running in applets
return false;
}
}
/*-----------*
* Variables *
*-----------*/
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* Directory to store information related to this sail in (if any).
*/
private volatile File dataDir;
/**
* Flag indicating whether the Sail has been initialized. Sails are initialized from {@link #initialize()
* initialization} until {@link #shutDown() shutdown}.
*/
private volatile boolean initialized = false;
/**
* Lock used to synchronize the initialization state of a sail.
*
* - write lock: initialize(), shutDown()
*
- read lock: getConnection()
*
*/
protected final ReentrantReadWriteLock initializationLock = new ReentrantReadWriteLock();
/**
* Connection timeout on shutdown (in ms). Defaults to {@link #DEFAULT_CONNECTION_TIMEOUT}.
*/
protected volatile long connectionTimeOut = DEFAULT_CONNECTION_TIMEOUT;
private long iterationCacheSyncThreshold = DEFAULT_ITERATION_SYNC_THRESHOLD;
/**
* Map used to track active connections and where these were acquired. The Throwable value may be null in
* case debugging was disable at the time the connection was acquired.
*/
private final Map activeConnections = new IdentityHashMap();
/*
* constructors
*/
public AbstractSail() {
super();
this.addSupportedIsolationLevel(IsolationLevels.READ_UNCOMMITTED);
this.addSupportedIsolationLevel(IsolationLevels.SERIALIZABLE);
}
/*---------*
* Methods *
*---------*/
/**
* Set connection timeout on shutdown (in ms).
*
* @param connectionTimeOut timeout (in ms)
*/
public void setConnectionTimeOut(long connectionTimeOut) {
this.connectionTimeOut = connectionTimeOut;
}
@Override
public void setDataDir(File dataDir) {
if (isInitialized()) {
throw new IllegalStateException("sail has already been initialized");
}
this.dataDir = dataDir;
}
@Override
public File getDataDir() {
return dataDir;
}
@Override
public String toString() {
if (dataDir == null) {
return super.toString();
}
else {
return dataDir.toString();
}
}
/**
* Checks whether the Sail has been initialized. Sails are initialized from {@link #initialize()
* initialization} until {@link #shutDown() shutdown}.
*
* @return true if the Sail has been initialized, false otherwise.
*/
protected boolean isInitialized() {
return initialized;
}
@Override
public void initialize()
throws SailException
{
initializationLock.writeLock().lock();
try {
logger.trace("is initialized: {}", isInitialized());
if (isInitialized()) {
throw new IllegalStateException(
"Sail has already been intialized. Ensure this Sail is being used via a Repository.");
}
initializeInternal();
initialized = true;
}
finally {
initializationLock.writeLock().unlock();
}
}
/**
* Do store-specific operations to initialize the store. The default implementation of this method does
* nothing.
*/
protected void initializeInternal()
throws SailException
{
}
@Override
public void shutDown()
throws SailException
{
initializationLock.writeLock().lock();
try {
if (!isInitialized()) {
return;
}
Map activeConnectionsCopy;
synchronized (activeConnections) {
// Check if any active connections exist. If so, wait for a grace
// period for them to finish.
if (!activeConnections.isEmpty()) {
logger.debug("Waiting for active connections to close before shutting down...");
try {
activeConnections.wait(connectionTimeOut);
}
catch (InterruptedException e) {
// ignore and continue
}
}
// Copy the current contents of the map so that we don't have to
// synchronize on activeConnections. This prevents a potential
// deadlock with concurrent calls to connectionClosed()
activeConnectionsCopy = new IdentityHashMap(activeConnections);
}
// Forcefully close any connections that are still open
for (Map.Entry entry : activeConnectionsCopy.entrySet()) {
SailConnection con = entry.getKey();
Throwable stackTrace = entry.getValue();
if (stackTrace == null) {
logger.warn(
"Closing active connection due to shut down; consider setting the {} system property",
DEBUG_PROP);
}
else {
logger.warn("Closing active connection due to shut down, connection was acquired in",
stackTrace);
}
try {
con.close();
}
catch (SailException e) {
logger.error("Failed to close connection", e);
}
}
// All connections should be closed now
synchronized (activeConnections) {
activeConnections.clear();
}
shutDownInternal();
}
finally {
initialized = false;
initializationLock.writeLock().unlock();
}
}
/**
* Do store-specific operations to ensure proper shutdown of the store.
*/
protected abstract void shutDownInternal()
throws SailException;
@Override
public SailConnection getConnection()
throws SailException
{
initializationLock.readLock().lock();
try {
if (!isInitialized()) {
throw new IllegalStateException("Sail is not initialized or has been shut down");
}
SailConnection connection = getConnectionInternal();
Throwable stackTrace = debugEnabled() ? new Throwable() : null;
synchronized (activeConnections) {
activeConnections.put(connection, stackTrace);
}
return connection;
}
finally {
initializationLock.readLock().unlock();
}
}
/**
* Returns a store-specific SailConnection object.
*
* @return A connection to the store.
*/
protected abstract SailConnection getConnectionInternal()
throws SailException;
/**
* Signals to the store that the supplied connection has been closed; called by
* {@link AbstractSailConnection#close()}.
*
* @param connection
* The connection that has been closed.
*/
protected void connectionClosed(SailConnection connection) {
synchronized (activeConnections) {
if (activeConnections.containsKey(connection)) {
activeConnections.remove(connection);
if (activeConnections.isEmpty()) {
// only notify waiting threads if all active connections have
// been closed.
activeConnections.notifyAll();
}
}
else {
logger.warn("tried to remove unknown connection object from store.");
}
}
}
/**
* Appends the provided {@link IsolationLevels} to the SAIL's list of supported isolation levels.
*
* @param level
* a supported IsolationLevel.
*/
protected void addSupportedIsolationLevel(IsolationLevels level) {
this.supportedIsolationLevels.add(level);
}
/**
* Removes all occurrences of the provided {@link IsolationLevels} in the list of supported Isolation
* levels.
*
* @param level
* the isolation level to remove.
*/
protected void removeSupportedIsolationLevel(IsolationLevel level) {
while (this.supportedIsolationLevels.remove(level)) {
}
}
/**
* Sets the list of supported {@link IsolationLevels}s for this SAIL. The list is expected to be ordered
* in increasing complexity.
*
* @param supportedIsolationLevels
* a list of supported isolation levels.
*/
protected void setSupportedIsolationLevels(List supportedIsolationLevels) {
this.supportedIsolationLevels = supportedIsolationLevels;
}
/**
* Sets the list of supported {@link IsolationLevels}s for this SAIL. The list is expected to be ordered
* in increasing complexity.
*
* @param supportedIsolationLevels
* a list of supported isolation levels.
*/
protected void setSupportedIsolationLevels(IsolationLevel... supportedIsolationLevels) {
this.supportedIsolationLevels = Arrays.asList(supportedIsolationLevels);
}
@Override
public List getSupportedIsolationLevels() {
return Collections.unmodifiableList(supportedIsolationLevels);
}
@Override
public IsolationLevel getDefaultIsolationLevel() {
return defaultIsolationLevel;
}
/**
* Sets the default {@link IsolationLevel} on which transactions in this Sail operate.
*
* @param defaultIsolationLevel
* The defaultIsolationLevel to set.
*/
public void setDefaultIsolationLevel(IsolationLevel defaultIsolationLevel) {
if (defaultIsolationLevel == null) {
throw new IllegalArgumentException("default isolation level may not be null");
}
this.defaultIsolationLevel = defaultIsolationLevel;
}
/**
* Retrieves the currently configured threshold for syncing query evaluation iteration caches to disk.
*
* @return Returns the iterationCacheSyncThreshold.
*/
public long getIterationCacheSyncThreshold() {
return iterationCacheSyncThreshold;
}
/**
* Set the threshold for syncing query evaluation iteration caches to disk.
*
* @param iterationCacheSyncThreshold
* The iterationCacheSyncThreshold to set.
*/
public void setIterationCacheSyncThreshold(long iterationCacheSyncThreshold) {
this.iterationCacheSyncThreshold = iterationCacheSyncThreshold;
}
}