/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.HashMap;
import org.hsqldb.lib.HashSet;
import org.hsqldb.lib.HsqlTimer;
import org.hsqldb.lib.IntKeyHashMap;
import org.hsqldb.lib.Iterator;
import org.hsqldb.lib.Notified;
import org.hsqldb.map.ValuePool;
import org.hsqldb.persist.HsqlProperties;

Handles initial attempts to connect to HSQLDB databases within the JVM (or a classloader within the JVM). Opens the database if it is not open or connects to it if it is already open. This allows the same database to be used by different instances of Server and by direct connections.

Maintains a map of Server instances and notifies each server when its database has shut down.

Maintains a reference to the timer used for file locks and logging.

Author:Fred Toussi (fredt@users dot sourceforge.net)
Version:2.5.0
Since:1.7.2
/** * Handles initial attempts to connect to HSQLDB databases within the JVM * (or a classloader within the JVM). Opens the database if it is not open * or connects to it if it is already open. This allows the same database to * be used by different instances of Server and by direct connections.<p> * * Maintains a map of Server instances and notifies each server when its * database has shut down.<p> * * Maintains a reference to the timer used for file locks and logging.<p> * * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.5.0 * @since 1.7.2 */
public class DatabaseManager { // Database and Server registry
provides unique ID's for the Databases currently in registry
/** provides unique ID's for the Databases currently in registry */
private static AtomicInteger dbIDCounter = new AtomicInteger();
name to Database mapping for mem: databases
/** name to Database mapping for mem: databases */
static final HashMap memDatabaseMap = new HashMap();
File to Database mapping for file: databases
/** File to Database mapping for file: databases */
static final HashMap fileDatabaseMap = new HashMap();
File to Database mapping for res: databases
/** File to Database mapping for res: databases */
static final HashMap resDatabaseMap = new HashMap();
id number to Database for Databases currently in registry
/** id number to Database for Databases currently in registry */
static final IntKeyHashMap databaseIDMap = new IntKeyHashMap();
Returns a vector containing the URI (type + path) for all the databases.
/** * Returns a vector containing the URI (type + path) for all the databases. */
public static Vector getDatabaseURIs() { Vector v = new Vector(); synchronized (databaseIDMap) { Iterator it = databaseIDMap.values().iterator(); while (it.hasNext()) { Database db = (Database) it.next(); v.addElement(db.getURI()); } } return v; }
Closes all the databases using the given mode.

CLOSEMODE_IMMEDIATELY = 1; CLOSEMODE_NORMAL = 2; CLOSEMODE_COMPACT = 3; CLOSEMODE_SCRIPT = 4;

/** * Closes all the databases using the given mode.<p> * * CLOSEMODE_IMMEDIATELY = 1; * CLOSEMODE_NORMAL = 2; * CLOSEMODE_COMPACT = 3; * CLOSEMODE_SCRIPT = 4; */
public static void closeDatabases(int mode) { synchronized (databaseIDMap) { Iterator it = databaseIDMap.values().iterator(); while (it.hasNext()) { Database db = (Database) it.next(); try { db.close(mode); } catch (HsqlException e) {} } } }
Used by server to open a new session
/** * Used by server to open a new session */
public static Session newSession(int dbID, String user, String password, String zoneString, int timeZoneSeconds) { Database db = null; synchronized (databaseIDMap) { db = (Database) databaseIDMap.get(dbID); } if (db == null) { return null; } Session session = db.connect(user, password, zoneString, timeZoneSeconds); session.isNetwork = true; return session; }
Used by in-process connections and by Servlet
/** * Used by in-process connections and by Servlet */
public static Session newSession(String type, String path, String user, String password, HsqlProperties props, String zoneString, int timeZoneSeconds) { Database db = getDatabase(type, path, props); return db.connect(user, password, zoneString, timeZoneSeconds); }
Returns an existing session. Used with repeat HTTP connections belonging to the same JDBC Connection / HSQL Session pair.
/** * Returns an existing session. Used with repeat HTTP connections * belonging to the same JDBC Connection / HSQL Session pair. */
public static Session getSession(int dbId, long sessionId) { Database db = null; synchronized (databaseIDMap) { db = (Database) databaseIDMap.get(dbId); } return db == null ? null : db.sessionManager.getSession(sessionId); }
Used by server to open or create a database
/** * Used by server to open or create a database */
public static int getDatabase(String type, String path, Notified server, HsqlProperties props) { Database db = getDatabase(type, path, props); registerServer(server, db); return db.databaseID; } public static Database getDatabase(int id) { synchronized (databaseIDMap) { return (Database) databaseIDMap.get(id); } } public static void shutdownDatabases(Notified server, int shutdownMode) { Database[] dbArray; synchronized (serverMap) { HashSet databases = (HashSet) serverMap.get(server); if (databases == null) { dbArray = new Database[0]; } else { dbArray = new Database[databases.size()]; databases.toArray(dbArray); } } for (int i = 0; i < dbArray.length; i++) { dbArray[i].close(shutdownMode); } }
This has to be improved once a threading model is in place. Current behaviour: Attempts to connect to different databases do not block. Two db's can open simultaneously. Attempts to connect to a db while it is opening or closing will block until the db is open or closed. At this point the db state is either DATABASE_ONLINE (after db.open() has returned) which allows a new connection to be made, or the state is DATABASE_SHUTDOWN which means the db can be reopened for the new connection).
/** * This has to be improved once a threading model is in place. * Current behaviour: * * Attempts to connect to different databases do not block. Two db's can * open simultaneously. * * Attempts to connect to a db while it is opening or closing will block * until the db is open or closed. At this point the db state is either * DATABASE_ONLINE (after db.open() has returned) which allows a new * connection to be made, or the state is DATABASE_SHUTDOWN which means * the db can be reopened for the new connection). * */
public static Database getDatabase(String dbtype, String path, HsqlProperties props) { // If the (type, path) pair does not correspond to a registered // instance, then getDatabaseObject() returns a newly constructed // and registered Database instance. // The database state will be DATABASE_SHUTDOWN, // which means that the switch below will attempt to // open the database instance. DatabaseType type = DatabaseType.get(dbtype); Database db = getDatabaseObject(type, path, props); synchronized (db) { switch (db.getState()) { case Database.DATABASE_ONLINE : break; case Database.DATABASE_SHUTDOWN : // if the database was shutdown while this attempt // was waiting, add the database back to the registry if (lookupDatabaseObject(type, path) == null) { addDatabaseObject(type, path, db); } db.open(); break; // This state will currently not be reached as Database.Close() is // called while a lock is held on the database. // If we remove the lock from this method and a database is // being shutdown by a thread and in the meantime another thread // attempts to connect to the db. The threads could belong to // different server instances or be in-process. case Database.DATABASE_CLOSING : // this case will not be reached as the state is set and // cleared within the db.open() call above, which is called // from this synchronized block // it is here simply as a placeholder for future development case Database.DATABASE_OPENING : throw Error.error(ErrorCode.LOCK_FILE_ACQUISITION_FAILURE, ErrorCode.M_DatabaseManager_getDatabase); } } return db; } private static synchronized Database getDatabaseObject(DatabaseType type, String path, HsqlProperties props) { Database db; String key = path; HashMap databaseMap; switch (type) { case DB_FILE : { databaseMap = fileDatabaseMap; key = filePathToKey(path); synchronized (databaseMap) { db = (Database) databaseMap.get(key); if (db == null) { if (databaseMap.size() > 0) { Iterator it = databaseMap.keySet().iterator(); while (it.hasNext()) { String current = (String) it.next(); if (key.equalsIgnoreCase(current)) { key = current; break; } } } } } break; } case DB_RES : { databaseMap = resDatabaseMap; break; } case DB_MEM : { databaseMap = memDatabaseMap; break; } default : throw Error.runtimeError(ErrorCode.U_S0500, "DatabaseManager"); } synchronized (databaseMap) { db = (Database) databaseMap.get(key); } if (db == null) { db = new Database(type, path, key, props); db.databaseID = dbIDCounter.getAndIncrement(); synchronized (databaseIDMap) { databaseIDMap.put(db.databaseID, db); } synchronized (databaseMap) { databaseMap.put(key, db); } } return db; }
Looks up database of a given type and path in the registry. Returns null if there is none.
/** * Looks up database of a given type and path in the registry. Returns * null if there is none. */
public static synchronized Database lookupDatabaseObject(DatabaseType type, String path) { Object key = path; HashMap databaseMap; switch (type) { case DB_FILE : databaseMap = fileDatabaseMap; key = filePathToKey(path); break; case DB_RES : databaseMap = resDatabaseMap; break; case DB_MEM : databaseMap = memDatabaseMap; break; default : throw (Error.runtimeError(ErrorCode.U_S0500, "DatabaseManager")); } synchronized (databaseMap) { return (Database) databaseMap.get(key); } }
Adds a database to the registry.
/** * Adds a database to the registry. */
private static synchronized void addDatabaseObject(DatabaseType type, String path, Database db) { Object key = path; HashMap databaseMap; switch (type) { case DB_FILE : databaseMap = fileDatabaseMap; key = filePathToKey(path); break; case DB_RES : databaseMap = resDatabaseMap; break; case DB_MEM : databaseMap = memDatabaseMap; break; default : throw (Error.runtimeError(ErrorCode.U_S0500, "DatabaseManager")); } synchronized (databaseIDMap) { databaseIDMap.put(db.databaseID, db); } synchronized (databaseMap) { databaseMap.put(key, db); } }
Removes the database from registry.
/** * Removes the database from registry. */
static void removeDatabase(Database database) { int dbID = database.databaseID; DatabaseType type = database.getType(); String path = database.getPath(); Object key = path; HashMap databaseMap; notifyServers(database); if (type == DatabaseType.DB_FILE) { databaseMap = fileDatabaseMap; key = filePathToKey(path); } else if (type == DatabaseType.DB_RES) { databaseMap = resDatabaseMap; } else if (type == DatabaseType.DB_MEM) { databaseMap = memDatabaseMap; } else { throw (Error.runtimeError(ErrorCode.U_S0500, "DatabaseManager")); } boolean isEmpty = false; synchronized (databaseIDMap) { databaseIDMap.remove(dbID); isEmpty = databaseIDMap.isEmpty(); } synchronized (databaseMap) { databaseMap.remove(key); } if (isEmpty) { ValuePool.resetPool(); } }
Maintains a map of servers to sets of databases. Servers register each of their databases. When a database is shutdown, all the servers accessing it are notified. The database is then removed form the sets for all servers and the servers that have no other database are removed from the map.
/** * Maintains a map of servers to sets of databases. * Servers register each of their databases. * When a database is shutdown, all the servers accessing it are notified. * The database is then removed form the sets for all servers and the * servers that have no other database are removed from the map. */
static final HashMap serverMap = new HashMap();
Deregisters a server completely.
/** * Deregisters a server completely. */
public static void deRegisterServer(Notified server) { synchronized (serverMap) { serverMap.remove(server); } }
Registers a server as serving a given database.
/** * Registers a server as serving a given database. */
private static void registerServer(Notified server, Database db) { synchronized (serverMap) { if (!serverMap.containsKey(server)) { serverMap.put(server, new HashSet()); } HashSet databases = (HashSet) serverMap.get(server); databases.add(db); } }
Notifies all servers that serve the database that the database has been shutdown.
/** * Notifies all servers that serve the database that the database has been * shutdown. */
private static void notifyServers(Database db) { Notified[] servers; synchronized (serverMap) { servers = new Notified[serverMap.size()]; serverMap.keysToArray(servers); } for (int i = 0; i < servers.length; i++) { Notified server = servers[i]; HashSet databases; boolean removed = false; synchronized (serverMap) { databases = (HashSet) serverMap.get(server); } if (databases != null) { synchronized (databases) { removed = databases.remove(db); } } if (removed) { server.notify(db.databaseID); } } } static boolean isServerDB(Database db) { Iterator it = serverMap.keySet().iterator(); for (; it.hasNext(); ) { Notified server = (Notified) it.next(); HashSet databases = (HashSet) serverMap.get(server); if (databases.contains(db)) { return true; } } return false; } // Timer private static final HsqlTimer timer = new HsqlTimer(); public static HsqlTimer getTimer() { return timer; } // converts file path to database lookup key, converting any // thrown exception to an HsqlException in the process private static String filePathToKey(String path) { try { return FileUtil.getFileUtil().canonicalPath(path); } catch (Exception e) { return path; } } }