/* * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007. * * Licensed under the Aduna BSD-style license. */ package org.openrdf.sail.helpers; import java.io.File; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.openrdf.sail.Sail; import org.openrdf.sail.SailChangedEvent; import org.openrdf.sail.SailChangedListener; import org.openrdf.sail.SailConnection; import org.openrdf.sail.SailException; /** * SailBase is 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 SailBase implements Sail { /*-----------* * Constants * *-----------*/ protected final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * Default connection timeout on shutdown: 20,000 milliseconds. */ protected final static long DEFAULT_CONNECTION_TIMEOUT = 20000L; // 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.openrdf.repository.debug"; static boolean debugEnabled() { String value = System.getProperty(DEBUG_PROP); return value != null && !value.equals("false"); } /*-----------* * Variables * *-----------*/ /** * Directory to store information related to this sail in. */ private File dataDir; /** * Flag indicating whether the Sail is shutting down. */ private boolean shutDownInProgress = false; /** * Connection timeout on shutdown (in ms). Defaults to * {@link #DEFAULT_CONNECTION_TIMEOUT}. */ protected long connectionTimeOut = DEFAULT_CONNECTION_TIMEOUT; /** * 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 Map<SailConnection, Throwable> activeConnections = new IdentityHashMap<SailConnection, Throwable>(); /** * Objects that should be notified of changes to the data in this Sail. */ private Set<SailChangedListener> sailChangedListeners = new HashSet<SailChangedListener>(0); /*---------* * Methods * *---------*/ public void setDataDir(File dataDir) { this.dataDir = dataDir; } public File getDataDir() { return dataDir; } public SailConnection getConnection() throws SailException { if (shutDownInProgress) { throw new IllegalStateException("shut down in progress"); } SailConnection connection = getConnectionInternal(); Throwable stackTrace = debugEnabled() ? new Throwable() : null; activeConnections.put(connection, stackTrace); return connection; } /** * returns a store-specific SailConnection object. * * @return a SailConnection * @throws SailException */ protected abstract SailConnection getConnectionInternal() throws SailException; public void shutDown() throws SailException { // indicate no more new connections should be given out. shutDownInProgress = true; synchronized (activeConnections) { // check if any active connections exist, if so, wait for a grace // period for them to finish. if (!activeConnections.isEmpty()) { logger.info("Waiting for active connections to close before shutting down..."); try { activeConnections.wait(DEFAULT_CONNECTION_TIMEOUT); } catch (InterruptedException e) { // ignore and continue } } // Forcefully close any connections that are still open Iterator<Map.Entry<SailConnection, Throwable>> iter = activeConnections.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<SailConnection, Throwable> entry = iter.next(); SailConnection con = entry.getKey(); Throwable stackTrace = entry.getValue(); iter.remove(); 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); } } } try { shutDownInternal(); } finally { shutDownInProgress = false; } } /** * Do store-specific operations to ensure proper shutdown of the store. * * @throws SailException */ protected abstract void shutDownInternal() throws SailException; /** * Signals to the store that the supplied connection has been closed. * * @param connection */ 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."); } } } public void addSailChangedListener(SailChangedListener listener) { synchronized (sailChangedListeners) { sailChangedListeners.add(listener); } } public void removeSailChangedListener(SailChangedListener listener) { synchronized (sailChangedListeners) { sailChangedListeners.remove(listener); } } /** * Notifies all registered SailChangedListener's of changes to the contents * of this Sail. */ public void notifySailChanged(SailChangedEvent event) { synchronized (sailChangedListeners) { for (SailChangedListener l : sailChangedListeners) { l.sailChanged(event); } } } }