001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import java.util.Vector;
034:
035: import org.hsqldb.lib.FileUtil;
036: import org.hsqldb.lib.HashMap;
037: import org.hsqldb.lib.HashSet;
038: import org.hsqldb.lib.HsqlTimer;
039: import org.hsqldb.lib.IntKeyHashMap;
040: import org.hsqldb.lib.Iterator;
041: import org.hsqldb.persist.HsqlProperties;
042: import org.hsqldb.store.ValuePool;
043:
044: /**
045: * Handles initial attempts to connect to HSQLDB databases within the JVM
046: * (or a classloader within the JVM). Opens the database if it is not open
047: * or connects to it if it is already open. This allows the same database to
048: * be used by different instances of Server and by direct connections.<p>
049: *
050: * Maintains a map of Server instances and notifies each server when its
051: * database has shut down.<p>
052: *
053: * Maintains a reference to the timer used for file locks and logging.<p>
054: *
055: * @author fredt@users
056: * @version 1.8.0
057: * @since 1.7.2
058: */
059: public class DatabaseManager {
060:
061: // Database and Server registry
062:
063: /** provides unique ID's for the Databases currently in registry */
064: private static int dbIDCounter;
065:
066: /** name to Database mapping for mem: databases */
067: static final HashMap memDatabaseMap = new HashMap();
068:
069: /** File to Database mapping for file: databases */
070: static final HashMap fileDatabaseMap = new HashMap();
071:
072: /** File to Database mapping for res: databases */
073: static final HashMap resDatabaseMap = new HashMap();
074:
075: /** id number to Database for Databases currently in registry */
076: static final IntKeyHashMap databaseIDMap = new IntKeyHashMap();
077:
078: /**
079: * Returns a vector containing the URI (type + path) for all the databases.
080: */
081: public static Vector getDatabaseURIs() {
082:
083: Vector v = new Vector();
084: Iterator it = databaseIDMap.values().iterator();
085:
086: while (it.hasNext()) {
087: Database db = (Database) it.next();
088:
089: v.addElement(db.getURI());
090: }
091:
092: return v;
093: }
094:
095: /**
096: * Closes all the databases using the given mode.<p>
097: *
098: * CLOSEMODE_IMMEDIATELY = -1;
099: * CLOSEMODE_NORMAL = 0;
100: * CLOSEMODE_COMPACT = 1;
101: * CLOSEMODE_SCRIPT = 2;
102: */
103: public static void closeDatabases(int mode) {
104:
105: Iterator it = databaseIDMap.values().iterator();
106:
107: while (it.hasNext()) {
108: Database db = (Database) it.next();
109:
110: try {
111: db.close(mode);
112: } catch (HsqlException e) {
113: }
114: }
115: }
116:
117: /**
118: * Used by server to open a new session
119: */
120: static Session newSession(int dbID, String user, String password)
121: throws HsqlException {
122:
123: Database db = (Database) databaseIDMap.get(dbID);
124:
125: return db == null ? null : db.connect(user, password);
126: }
127:
128: /**
129: * Used by in-process connections and by Servlet
130: */
131:
132: // loosecannon1@users 1.7.2 patch properties on the JDBC URL
133: public static Session newSession(String type, String path,
134: String user, String password, HsqlProperties props)
135: throws HsqlException {
136:
137: Database db = getDatabase(type, path, props);
138:
139: return db == null ? null : db.connect(user, password);
140: }
141:
142: /**
143: * Returns an existing session. Used with repeat HTTP connections
144: * belonging to the same JDBC Conenction / HSQL Session pair.
145: */
146: static Session getSession(int dbId, int sessionId) {
147:
148: Database db = (Database) databaseIDMap.get(dbId);
149:
150: return db == null ? null : db.sessionManager
151: .getSession(sessionId);
152: }
153:
154: /**
155: * Used by server to open or create a database
156: */
157:
158: // loosecannon1@users 1.7.2 patch properties on the JDBC URL
159: static int getDatabase(String type, String path, Server server,
160: HsqlProperties props) throws HsqlException {
161:
162: Database db = getDatabase(type, path, props);
163:
164: registerServer(server, db);
165:
166: return db.databaseID;
167: }
168:
169: /**
170: * This has to be improved once a threading model is in place.
171: * Current behaviour:
172: *
173: * Attempts to connect to different databases do not block. Two db's can
174: * open simultaneously.
175: *
176: * Attempts to connect to a db while it is opening or closing will block
177: * until the db is open or closed. At this point the db state is either
178: * DATABASE_ONLINE (after db.open() has returned) which allows a new
179: * connection to be made, or the state is DATABASE_SHUTDOWN which means
180: * the db can be reopened for the new connection).
181: *
182: */
183:
184: // loosecannon1@users 1.7.2 patch properties on the JDBC URL
185: static Database getDatabase(String type, String path,
186: HsqlProperties props) throws HsqlException {
187:
188: // If the (type, path) pair does not correspond to a registered
189: // instance, then getDatabaseObject() returns a newly constructed
190: // and registered Database instance.
191: // The database state will be DATABASE_SHUTDOWN,
192: // which means that the switch below will attempt to
193: // open the database instance.
194: Database db = getDatabaseObject(type, path, props);
195:
196: synchronized (db) {
197: switch (db.getState()) {
198:
199: case Database.DATABASE_ONLINE:
200: break;
201:
202: case Database.DATABASE_SHUTDOWN:
203:
204: // if the database was shutdown while this attempt
205: // was waiting, add the database back to the registry
206: if (lookupDatabaseObject(type, path) == null) {
207: addDatabaseObject(type, path, db);
208: }
209:
210: db.open();
211: break;
212:
213: // This state will currently not be reached as Database.Close() is
214: // called while a lock is held on the database.
215: // If we remove the lock from this method and a database is
216: // being shutdown by a thread and in the meantime another thread
217: // attempts to connect to the db. The threads could belong to
218: // different server instances or be in-process.
219: case Database.DATABASE_CLOSING:
220:
221: // this case will not be reached as the state is set and
222: // cleared within the db.open() call above, which is called
223: // from this synchronized block
224: // it is here simply as a placeholder for future development
225: case Database.DATABASE_OPENING:
226: throw Trace.error(Trace.DATABASE_ALREADY_IN_USE,
227: Trace.DatabaseManager_getDatabase);
228: }
229: }
230:
231: return db;
232: }
233:
234: // loosecannon1@users 1.7.2 patch properties on the JDBC URL
235: private static synchronized Database getDatabaseObject(String type,
236: String path, HsqlProperties props) throws HsqlException {
237:
238: Database db;
239: String key = path;
240: HashMap databaseMap;
241:
242: if (type == DatabaseURL.S_FILE) {
243: databaseMap = fileDatabaseMap;
244: key = filePathToKey(path);
245: } else if (type == DatabaseURL.S_RES) {
246: databaseMap = resDatabaseMap;
247: } else if (type == DatabaseURL.S_MEM) {
248: databaseMap = memDatabaseMap;
249: } else {
250: throw Trace.runtimeError(
251: Trace.UNSUPPORTED_INTERNAL_OPERATION,
252: "DatabaseManager.getDatabaseObject");
253: }
254:
255: db = (Database) databaseMap.get(key);
256:
257: if (db == null) {
258: db = new Database(type, path, type + key, props);
259: db.databaseID = dbIDCounter;
260:
261: databaseIDMap.put(dbIDCounter, db);
262:
263: dbIDCounter++;
264:
265: databaseMap.put(key, db);
266: }
267:
268: return db;
269: }
270:
271: /**
272: * Looks up database of a given type and path in the registry. Returns
273: * null if there is none.
274: */
275: private static synchronized Database lookupDatabaseObject(
276: String type, String path) throws HsqlException {
277:
278: Object key = path;
279: HashMap databaseMap;
280:
281: if (type == DatabaseURL.S_FILE) {
282: databaseMap = fileDatabaseMap;
283: key = filePathToKey(path);
284: } else if (type == DatabaseURL.S_RES) {
285: databaseMap = resDatabaseMap;
286: } else if (type == DatabaseURL.S_MEM) {
287: databaseMap = memDatabaseMap;
288: } else {
289: throw (Trace.runtimeError(
290: Trace.UNSUPPORTED_INTERNAL_OPERATION,
291: "DatabaseManager.lookupDatabaseObject()"));
292: }
293:
294: return (Database) databaseMap.get(key);
295: }
296:
297: /**
298: * Adds a database to the registry. Returns
299: * null if there is none.
300: */
301: private static synchronized void addDatabaseObject(String type,
302: String path, Database db) throws HsqlException {
303:
304: Object key = path;
305: HashMap databaseMap;
306:
307: if (type == DatabaseURL.S_FILE) {
308: databaseMap = fileDatabaseMap;
309: key = filePathToKey(path);
310: } else if (type == DatabaseURL.S_RES) {
311: databaseMap = resDatabaseMap;
312: } else if (type == DatabaseURL.S_MEM) {
313: databaseMap = memDatabaseMap;
314: } else {
315: throw Trace.runtimeError(
316: Trace.UNSUPPORTED_INTERNAL_OPERATION,
317: "DatabaseManager.addDatabaseObject()");
318: }
319:
320: databaseIDMap.put(db.databaseID, db);
321: databaseMap.put(key, db);
322: }
323:
324: /**
325: * Removes the database from registry.
326: */
327: static void removeDatabase(Database database) {
328:
329: int dbID = database.databaseID;
330: String type = database.getType();
331: String path = database.getPath();
332: Object key = path;
333: HashMap databaseMap;
334:
335: notifyServers(database);
336:
337: if (type == DatabaseURL.S_FILE) {
338: databaseMap = fileDatabaseMap;
339:
340: // boucherb@users 20040124 - patch 1.7.2
341: // Under the current contract, it's essentially impossible for an
342: // exception to get thrown here, because the database could not
343: // have been registered successfully before hand using the same
344: // path
345: //
346: // Eventually, we might think about storing the key with the
347: // database instance so as to avoid this unnecessary additional
348: // conversion and highly unlikely corner case handling.
349: try {
350: key = filePathToKey(path);
351: } catch (HsqlException e) {
352: Iterator it = databaseMap.keySet().iterator();
353: Object foundKey = null;
354:
355: while (it.hasNext()) {
356: Object currentKey = it.next();
357:
358: if (databaseMap.get(currentKey) == database) {
359: foundKey = currentKey;
360:
361: break;
362: }
363: }
364:
365: if (foundKey == null) {
366:
367: // ??? return;
368: } else {
369: key = foundKey;
370: }
371: }
372: } else if (type == DatabaseURL.S_RES) {
373: databaseMap = resDatabaseMap;
374: } else if (type == DatabaseURL.S_MEM) {
375: databaseMap = memDatabaseMap;
376: } else {
377: throw (Trace.runtimeError(
378: Trace.UNSUPPORTED_INTERNAL_OPERATION,
379: "DatabaseManager.lookupDatabaseObject()"));
380: }
381:
382: databaseIDMap.remove(dbID);
383: databaseMap.remove(key);
384:
385: if (databaseIDMap.isEmpty()) {
386: ValuePool.resetPool();
387: }
388: }
389:
390: /**
391: * Maintains a map of servers to sets of databases.
392: * Servers register each of their databases.
393: * When a database is shutdown, all the servers accessing it are notified.
394: * The database is then removed form the sets for all servers and the
395: * servers that have no other database are removed from the map.
396: */
397: static HashMap serverMap = new HashMap();
398:
399: /**
400: * Deregisters a server completely.
401: */
402: static void deRegisterServer(Server server) {
403: serverMap.remove(server);
404: }
405:
406: /**
407: * Deregisters a server as serving a given database. Not yet used.
408: */
409: private static void deRegisterServer(Server server, Database db) {
410:
411: Iterator it = serverMap.values().iterator();
412:
413: for (; it.hasNext();) {
414: HashSet databases = (HashSet) it.next();
415:
416: databases.remove(db);
417:
418: if (databases.isEmpty()) {
419: it.remove();
420: }
421: }
422: }
423:
424: /**
425: * Registers a server as serving a given database.
426: */
427: private static void registerServer(Server server, Database db) {
428:
429: if (!serverMap.containsKey(server)) {
430: serverMap.put(server, new HashSet());
431: }
432:
433: HashSet databases = (HashSet) serverMap.get(server);
434:
435: databases.add(db);
436: }
437:
438: /**
439: * Notifies all servers that serve the database that the database has been
440: * shutdown.
441: */
442: private static void notifyServers(Database db) {
443:
444: Iterator it = serverMap.keySet().iterator();
445:
446: for (; it.hasNext();) {
447: Server server = (Server) it.next();
448: HashSet databases = (HashSet) serverMap.get(server);
449:
450: if (databases.contains(db)) {
451: server.notify(ServerConstants.SC_DATABASE_SHUTDOWN,
452: db.databaseID);
453: }
454: }
455: }
456:
457: static boolean isServerDB(Database db) {
458:
459: Iterator it = serverMap.keySet().iterator();
460:
461: for (; it.hasNext();) {
462: Server server = (Server) it.next();
463: HashSet databases = (HashSet) serverMap.get(server);
464:
465: if (databases.contains(db)) {
466: return true;
467: }
468: }
469:
470: return false;
471: }
472:
473: // Timer
474: private static final HsqlTimer timer = new HsqlTimer();
475:
476: public static HsqlTimer getTimer() {
477: return timer;
478: }
479:
480: // converts file path to database lookup key, converting any
481: // thrown exception to an HsqlException in the process
482: private static String filePathToKey(String path)
483: throws HsqlException {
484:
485: try {
486: return FileUtil.canonicalPath(path);
487: } catch (Exception e) {
488: throw Trace.error(Trace.FILE_IO_ERROR, e.toString());
489: }
490: }
491: }
|