Source code: com/lutris/appserver/server/sql/StandardDatabaseManager.java
1 /*
2 * Enhydra Java Application Server Project
3 *
4 * The contents of this file are subject to the Enhydra Public License
5 * Version 1.1 (the "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy of the License on
7 * the Enhydra web site ( http://www.enhydra.org/ ).
8 *
9 * Software distributed under the License is distributed on an "AS IS"
10 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 * the License for the specific terms governing rights and limitations
12 * under the License.
13 *
14 * The Initial Developer of the Enhydra Application Server is Lutris
15 * Technologies, Inc. The Enhydra Application Server and portions created
16 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17 * All Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * $Id: StandardDatabaseManager.java,v 1.20.12.1 2000/10/19 17:59:05 jasona Exp $
22 */
23
24
25
26
27
28 package com.lutris.appserver.server.sql;
29
30 import com.lutris.util.*;
31 import com.lutris.appserver.server.Enhydra;
32 import com.lutris.appserver.server.sql.*;
33 import com.lutris.appserver.server.sql.standard.StandardLogicalDatabase;
34 import com.lutris.appserver.server.sql.oracle.OracleLogicalDatabase;
35 import com.lutris.appserver.server.sql.informix.InformixLogicalDatabase;
36 import com.lutris.appserver.server.sql.sybase.SybaseLogicalDatabase;
37 import com.lutris.appserver.server.sql.msql.MsqlLogicalDatabase;
38 import com.lutris.appserver.server.sql.postgresql.PostgreSQLLogicalDatabase;
39 import java.sql.*;
40 import java.util.Date;
41 import java.util.Hashtable;
42 import java.util.Enumeration;
43
44
45 /**
46 * The standard database manager implementation. A database manager
47 * manages logical databases. It provides a single object from which
48 * database connections, object ids (OIDs), transactions and queries
49 * can be obtained. The configuration file specifies what logical
50 * database to create.
51 * <P>
52 * The configuration data is specified as follows:
53 * <UL>
54 * <LI> <B><CODE>DatabaseManager.Databases</CODE></B> -
55 * A list of logical SQL database names.
56 * <LI> <B><CODE>DatabaseManager.DefaultDatabase</CODE></B> -
57 * The default logical database used by this application. Optional,
58 * if not specified, then the first entry in
59 * "DatabaseManager.Databases" is used.
60 * <LI> <B><CODE>DatabaseManager.Debug</CODE></B> -
61 * Specify true to enable Query and Transaction logging, false to
62 * disable it. Optional, false if not specified.
63 * </UL>
64 * For each logical database, there is a set of entry names in the form
65 * <B><CODE>DatabaseManager.DB.<I>dbname</I></CODE></B>.
66 * Where <CODE><I>dbname</I></CODE> is one of the listed logical databases.
67 * <P>
68 * <B><CODE>DatabaseManager.DB.<I>dbname</I>.ClassType</CODE></B> -
69 * This is an optional field which specifies the class of the logical
70 * database implementation or a symbolic name if one on the standard types
71 * are selected. This is recommended because although JDBC abstracts the
72 * data access, the functionality of each database is slightly different
73 * and this parameter allows for optimised usage. The following are
74 * standard types:
75 * <UL>
76 * <LI> <I>Oracle</I> - For optimized Oracle 7/8 usage.
77 * <LI> <I>Informix</I> - For optimized Informix usage.
78 * <LI> <I>Sybase</I> - For optimized Sybase usage.
79 * <LI> <I>Msql</I> - For optimized Microsoft MSQL usage.
80 * <LI> <I>Standard</I> - For all other JDBC databases. <B>(Default)</B>
81 * <LI> <I>class</I> - For vendor supplied database classes.
82 * </UL>
83 * <P>
84 * Note that since a single SQL user is used to access the database by
85 * the entire application, connections maybe kept open, thus saving this
86 * overhead on each request. Connections are opened as desired until
87 * the maximum configured is reached.
88 * <P>
89 * If a thread needs to process a transaction, it requests a connection.
90 * If none are available, then a new connection is created up to the
91 * configured maximum. A thread is queued waiting for a connection to be
92 * returned if a new connection can't be created.
93 * <P>
94 * Note also that multiple logical names may map to the same actual
95 * database, making it easy to migrate databases if necessary and to
96 * provide access through multiple users.
97 *
98 * @version $Revision: 1.20.12.1 $
99 * @since LBS1.8
100 * @author Paul Morgan
101 * @author Kyle Clark
102 */
103 public class StandardDatabaseManager implements DatabaseManager {
104
105 /**
106 * Table of named logical databases.
107 */
108 private Hashtable logicalDatabases = new Hashtable ();
109
110 /**
111 * Default logical database.
112 */
113 private LogicalDatabase defaultLogicalDatabase = null;
114
115 /**
116 * Controls debugging for Transactions and Queries.
117 */
118 protected boolean debug = false;
119
120
121 /**
122 * Creates a new <code>DatabaseManager</code> object and configures
123 * the logical databases defined in the config file.
124 *
125 * @param config
126 * The configuration data for logical databases.
127 * @exception ConfigException
128 * If there is an error in the configuration file.
129 * @exception DatabaseManagerException
130 * If a logical database name is specified twice in the configuration file.
131 * @exception SQLException
132 * If a SQL error occurs.
133 */
134 public StandardDatabaseManager(Config config)
135 throws ConfigException, DatabaseManagerException, SQLException {
136
137 String[] databases = config.getStrings ("Databases");
138 if (databases == null)
139 return;
140
141 /**
142 * Configure each logical database.
143 */
144 Config dbConfig;
145 for (int idx = 0; idx < databases.length; idx++) {
146 String dbName = databases[idx];
147 try {
148 dbConfig = (Config)config.getSection("DB." + dbName);
149 } catch (KeywordValueException except) {
150 throw new ConfigException("No DatabaseManager.DB." + dbName + " defined in config file.");
151 }
152
153 if (logicalDatabases.get (dbName) != null) {
154 throw new DatabaseManagerException
155 ("duplicate logical database name: \"" + dbName + "\"");
156 }
157
158 LogicalDatabase logicalDatabase =
159 loadLogicalDatabase (dbName, dbConfig);
160
161 logicalDatabases.put (dbName, logicalDatabase);
162 }
163
164 /**
165 * Set default database if supplied.
166 */
167 String defaultDB = config.getString ("DefaultDatabase", databases[0]);
168 setDefaultDatabase(defaultDB);
169
170 /**
171 * Set debug logging of Queries and Transactions.
172 */
173 boolean debugLogging = config.getBoolean ("Debug", false);
174 setDebugLogging(debugLogging);
175
176 /**
177 * Set the oid and version column names in CoreDO.
178 * Note that these could be configured for each logical
179 * database but this would require some complex extensions
180 * to DBQuery and DBTransaction in order to ensure that
181 * the correct values were always being used without
182 * creating race conditions.
183 * This means that the limitation is currently that all
184 * databases accessed by your application must use the
185 * same column names.
186 */
187 String oidColumnName = config.getString("ObjectIdColumnName", null);
188 if (oidColumnName != null) {
189 CoreDO.setOIdColumnName(oidColumnName);
190 }
191 String versionColumnName = config.getString("VersionColumnName", null);
192 if (versionColumnName != null) {
193 CoreDO.setVersionColumnName(versionColumnName);
194 }
195 }
196
197
198 /**
199 * Actually load the specified logical database. This method provides
200 * an easy way to override the default behavour.
201 *
202 * @return
203 * The logical database.
204 * @exception DatabaseManagerException
205 * if an error occurs creating the logical database.
206 */
207 public LogicalDatabase loadLogicalDatabase (String dbName,
208 Config dbConfig)
209 throws DatabaseManagerException {
210
211 LogicalDatabase lDB = null;
212 try {
213 String dbClassName = dbConfig.getString ("ClassType", "Standard");
214 if (dbClassName.equals ("Standard")) {
215 lDB = new StandardLogicalDatabase (dbName, dbConfig);
216 } else if (dbClassName.equals ("Oracle")) {
217 lDB = new OracleLogicalDatabase (dbName, dbConfig);
218 } else if (dbClassName.equals ("Informix")) {
219 lDB = new InformixLogicalDatabase (dbName, dbConfig);
220 } else if (dbClassName.equals ("Sybase")) {
221 lDB = new SybaseLogicalDatabase (dbName, dbConfig);
222 } else if (dbClassName.equals ("Msql")) {
223 lDB = new MsqlLogicalDatabase (dbName, dbConfig);
224 } else if (dbClassName.equals ("PostgreSQL")) {
225 lDB = new PostgreSQLLogicalDatabase (dbName, dbConfig);
226 } else {
227 // Must be a custom class. Make instance and call init
228 // method to configure. Also we must use the application
229 // class loader.
230 ClassLoader appClassLoader =
231 Enhydra.getApplication().getClass().getClassLoader();
232 Class dbClass = appClassLoader.loadClass(dbClassName);
233 lDB = (LogicalDatabase) dbClass.newInstance ();
234 lDB.init (dbName, dbConfig);
235 }
236 } catch (Exception except) {
237 throw new DatabaseManagerException ("Could not create logical database " + dbName, except);
238 }
239
240 return lDB;
241 }
242
243
244 /**
245 * Allocate a connection to a thread. The connection should be returned
246 * to the allocator by calling its
247 * <a href=com.lutris.appserver.server.sql.DBConnection#release>
248 * release()</a> function. A thread will wait if
249 * no connections are available.
250 * Interupted exceptions are converted to
251 * errors.
252 * N.B. Can't be synchronized, as connection allocator
253 * <CODE>allocate</CODE> may wait.
254 *
255 * @param dbName
256 * Logical name of the database to allocate a connection to.
257 * @return
258 * The allocated connection object.
259 * @exception DatabaseManagerException
260 * If a nonexistent logical database name is supplied.
261 * @exception SQLException
262 * If a SQL error occures.
263 */
264 public DBConnection allocateConnection (String dbName)
265 throws DatabaseManagerException, SQLException {
266
267 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
268 return logicalDatabase.allocateConnection();
269 }
270
271
272 /**
273 * Allocate a connection to a thread. The connection should be returned
274 * to the allocator by calling its
275 * <a href=com.lutris.appserver.server.sql.DBConnection#release>
276 * release()</a> function. A thread will wait if
277 * no connections are available.
278 * Interupted exceptions are converted to
279 * errors. The connection is allocated from the default logical
280 * database.
281 *
282 * @return
283 * The allocated connection object.
284 * @exception DatabaseManagerException
285 * If no default logical database has been set.
286 * @exception SQLException
287 * If a SQL error occurs.
288 * @see
289 * #setDefaultDatabase
290 */
291 public DBConnection allocateConnection ()
292 throws DatabaseManagerException, SQLException {
293
294 if (defaultLogicalDatabase == null)
295 throw new DatabaseManagerException ("Default logical database " +
296 "has not been specified.");
297 return defaultLogicalDatabase.allocateConnection ();
298 }
299
300
301 /**
302 * Allocate an object id from the specified logical database.
303 *
304 * @param name
305 * Logical name of the database from which to obtain an object id.
306 * @return The allocated unique OID
307 * @exception DatabaseManagerException
308 * If a nonexistent logical database name is supplied.
309 * @exception ObjectIdException
310 * If a problem (e.g. SQL error) occured in obtaining the OID.
311 */
312 public ObjectId allocateObjectId (String dbName)
313 throws DatabaseManagerException, ObjectIdException {
314
315 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
316 return logicalDatabase.allocateObjectId();
317 }
318
319
320 /**
321 * Allocate an object id from the specified logical database.
322 *
323 * @return The allocated connection object.
324 * @exception DatabaseManagerException
325 * If a nonexistent default logical database has been set.
326 * @exception ObjectIdException
327 * If a problem (e.g. SQL error) occured in obtaining the OID.
328 * @see
329 * #setDefaultDatabase
330 */
331 public ObjectId allocateObjectId ()
332 throws DatabaseManagerException, ObjectIdException {
333
334 if (defaultLogicalDatabase == null)
335 throw new DatabaseManagerException ("Default logical database " +
336 "has not been specified.");
337 return defaultLogicalDatabase.allocateObjectId ();
338 }
339
340
341 /**
342 * Create a transaction object for the specified logical database.
343 *
344 * @param dbName
345 * Logical name of the database from which to obtain a transaction.
346 * @return The transaction
347 * @exception DatabaseManagerException
348 * If a nonexistent or invalid logical database name is supplied.
349 * @exception SQLException
350 * If a problem occured creating the transaction.
351 */
352 public DBTransaction createTransaction (String dbName)
353 throws DatabaseManagerException, SQLException {
354
355 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
356 return logicalDatabase.createTransaction ();
357 }
358
359
360 /**
361 * Create a transaction object for the default logical database.
362 *
363 * @param dbName
364 * Logical name of the database from which to obtain a transaction.
365 * @return The transaction
366 * @exception DatabaseManagerException
367 * If a nonexistent default logical database has been set.
368 * @exception SQLException
369 * If a problem occured creating the transaction.
370 * @see
371 * #setDefaultDatabase
372 */
373 public DBTransaction createTransaction ()
374 throws DatabaseManagerException, SQLException {
375
376 if (defaultLogicalDatabase == null)
377 throw new DatabaseManagerException ("Default logical database " +
378 "has not been specified.");
379 return defaultLogicalDatabase.createTransaction ();
380 }
381
382
383 /**
384 * Create a query object for the specified logical database.
385 *
386 * @param dbName
387 * Logical name of the database from which to obtain a query.
388 * @return The query
389 * @exception DatabaseManagerException
390 * If a nonexistent or invalid logical database name is supplied.
391 * @exception SQLException
392 * If a problem occured creating the query.
393 */
394 public DBQuery createQuery (String dbName)
395 throws DatabaseManagerException, SQLException {
396
397 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
398 return logicalDatabase.createQuery ();
399 }
400
401
402 /**
403 * Create a query object for the default logical database.
404 *
405 * @param dbName
406 * Logical name of the database from which to obtain a query.
407 * @return The query
408 * @exception DatabaseManagerException
409 * If a nonexistent default logical database has been set.
410 * @exception SQLException
411 * If a problem occured creating the query.
412 * @see
413 * #setDefaultDatabase
414 */
415 public DBQuery createQuery ()
416 throws DatabaseManagerException, SQLException {
417
418 if (defaultLogicalDatabase == null)
419 throw new DatabaseManagerException ("Default logical database " +
420 "has not been specified.");
421 return defaultLogicalDatabase.createQuery ();
422 }
423
424
425 /**
426 * Find the named logical database in hash table.
427 *
428 * @param dbName Logical name of the database to locate.
429 * @exception DatabaseManagerException If a nonexistant logical database
430 * name is supplied.
431 */
432 private LogicalDatabase findLogicalDatabase (String dbName)
433 throws DatabaseManagerException {
434
435 LogicalDatabase logicalDatabase =
436 (LogicalDatabase) logicalDatabases.get (dbName);
437 if (logicalDatabase == null) {
438 throw new DatabaseManagerException
439 ("unknown logical database name: \"" +
440 dbName + "\"");
441 }
442 return logicalDatabase;
443 }
444
445
446 /**
447 * Set the default logical database. This should only be called
448 * after the logical database
449 * (<A HREF=com.lutris.appserver.server.sql.LogicalDatabase></A>
450 * has been established.
451 *
452 * @param dbName The default logical database.
453 * @exception DatabaseManagerException
454 * if the logical database name is invalid or not found.
455 */
456 public void setDefaultDatabase (String dbName)
457 throws DatabaseManagerException {
458
459 defaultLogicalDatabase = findLogicalDatabase (dbName);
460 }
461
462 /**
463 * Shutdown the database manager. All logical databases will be
464 * shutdown and all connections closed.
465 */
466 public void shutdown () {
467
468 for (Enumeration keys = logicalDatabases.keys ();
469 keys.hasMoreElements();) {
470
471 LogicalDatabase logicalDatabase = (LogicalDatabase)
472 logicalDatabases.get ((String)keys.nextElement());
473
474 logicalDatabase.shutdown();
475 }
476 }
477
478
479 //====================================================================
480 // The following are primarily for management purposes...
481 //====================================================================
482
483 /**
484 * Returns the list of managed logical databases.
485 *
486 * @return List of logical database names.
487 */
488 public String[] getLogicalDatabaseNames () {
489
490 String[] names = new String[logicalDatabases.size()];
491 int idx = 0;
492 Enumeration keys = logicalDatabases.keys();
493 while (keys.hasMoreElements()) {
494 names[idx++] = (String) keys.nextElement();
495 }
496 return names;
497 }
498
499
500 /**
501 * Returns a description of the logical database type.
502 *
503 * @param dbName
504 * The logical database name.
505 * @return
506 * A text description of the logical database type.
507 * @exception DatabaseManagerException
508 * If a nonexistent logical database name is supplied.
509 */
510 public String getType(String dbName)
511 throws DatabaseManagerException {
512
513 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
514 return logicalDatabase.getType();
515 }
516
517
518 /**
519 * Gets the number of requests made to the database since startup time.
520 *
521 * @param The name of the logical database.
522 * @exception DatabaseManagerException
523 * If a nonexistent logical database name is supplied.
524 * @return The number of database requests since the server started.
525 */
526 public long getRequestCount (String dbName)
527 throws DatabaseManagerException {
528
529 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
530 return logicalDatabase.getRequestCount();
531 }
532
533
534 /**
535 * Gets the number of currently active connections.
536 *
537 * @param dbName The name of the logical database.
538 * @exception DatabaseManagerException
539 * If a nonexistent logical database name is supplied.
540 * @return The number of currently active connections.
541 */
542 public int getActiveConnectionCount (String dbName)
543 throws DatabaseManagerException {
544
545 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
546 return logicalDatabase.getActiveConnectionCount();
547 }
548
549
550 /**
551 * Gets the maximum number of concurent connections that existed
552 * at any time since this object was created, or
553 * <CODE>resetMaxConnectionCount()</CODE> was called.
554 * This is a historical highwater mark.
555 * If you do not implement this feature, return -1.
556 *
557 * @param dbName The name of the logical database.
558 * @exception DatabaseManagerException
559 * If a nonexistent logical database name is supplied.
560 * @return The highwater mark for number of connections, or -1.
561 */
562 public int getMaxConnectionCount (String dbName)
563 throws DatabaseManagerException {
564
565 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
566 return logicalDatabase.getMaxConnectionCount();
567 }
568
569
570 /**
571 * Gets the time when the maximum refered to by
572 * <CODE>maxConnectionCount()</CODE> occured.
573 *
574 * @param dbName The name of the logical database.
575 * @exception DatabaseManagerException
576 * If a nonexistent logical database name is supplied.
577 * @return The Date of when the maximum number of connections occured.
578 */
579 public Date getMaxConnectionCountDate (String dbName)
580 throws DatabaseManagerException {
581
582 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
583 return logicalDatabase.getMaxConnectionCountDate();
584 }
585
586
587 /**
588 * Reset the maximum connection count. See
589 * <CODE>maxConnectionCount()</CODE>. The highwater mark should be
590 * reset to the current number of connections.
591 *
592 * @param dbName The name of the logical database.
593 * @exception DatabaseManagerException
594 * If a nonexistent logical database name is supplied.
595 */
596 public void resetMaxConnectionCount (String dbName)
597 throws DatabaseManagerException {
598
599 LogicalDatabase logicalDatabase = findLogicalDatabase (dbName);
600 logicalDatabase.resetMaxConnectionCount();
601 return;
602 }
603
604
605 /**
606 * Turn debugging on or off.
607 *
608 * @param condition on of off.
609 */
610 public void setDebugLogging (boolean condition) {
611 debug = condition;
612 }
613 }