Source code: com/lutris/appserver/server/sql/standard/StandardConnectionAllocator.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: StandardConnectionAllocator.java,v 1.10.12.1 2000/10/19 17:59:06 jasona Exp $
22 */
23
24
25
26
27
28 package com.lutris.appserver.server.sql.standard;
29
30 import com.lutris.appserver.server.LBS;
31 import com.lutris.appserver.server.sql.*;
32 import com.lutris.logging.*;
33 import com.lutris.util.*;
34 import java.util.Date; // Must appear before java.sql.Date
35 import java.util.Stack;
36 import java.sql.*;
37
38
39 /**
40 * Manages a pool (set) of connections to a database. The pool is named
41 * as a logical database. By naming a pool, this allows connection resource
42 * control to be on a finer grain that a database and allows for easier
43 * migration to multiple databases. One or more pools can map to the same
44 * actual database. A connection considered part of the pool, even if its
45 * allocated to a thread. These objects are all publicly accessed via the
46 * Database Manager, not directly.
47 * <P>
48 * If an error occurs in a connection, it is dropped from the pool. The
49 * process using the connection has already received an error which aborts
50 * the work in progress. By dropping the connection, waiting threads are
51 * restarted. If something is wrong with the database (e.g. server is down),
52 * they will recieve errors and also be aborted. A generation number is used
53 * to close down all connections that were open when the error occured,
54 * allowing new connections to be allocated.
55 * <P>
56 * The configuration data is specified in the section:
57 * <B><CODE>DatabaseManager.DB.<I>dbName</I>.Connection</CODE></B>
58 * <P>
59 * <I>Configuration sub fields are:</I>
60 * <UL>
61 * <LI> <B><CODE>Url</CODE></B> -
62 * The JDBC URLof the database. Manditary.
63 * E.g. "jdbc:sequelink://dbHost:4000/[Informix];Database=dummy"
64 * <LI> <B><CODE>User</CODE></B> -
65 * The database users used to access the database. Manditary.
66 * <LI> <B><CODE>Password</CODE></B> -
67 * The database user's password. Manditary.
68 * <LI> <B><CODE>MaxPoolSize</CODE></B> -
69 * The maximum number of open connections to the database. Optional,
70 * if not specified, then it default to 0. A value of 0 means that
71 * connections are allocated indefinitely or until the database (JDBC)
72 * refuses any new connections.
73 * <LI> <B><CODE>Logging</CODE></B> -
74 * Specify true to enable SQL logging, false to disable it. Optional,
75 * false if not specified.
76 * <LI> <B><CODE>AllocationTimeout</CODE></B> -
77 * The Maximum amount of time that a thread will wait for
78 * a connection from the connection allocator before an exception is
79 * thrown. This will prevent possible dead locks. The time out is in
80 * milliseconds. If the time out is <= 0, the allocation of connections
81 * will wait indefinitely. Optional, if not specified, then it
82 * defaults to 1000 (ms).
83 * <LI> <B><CODE>QueryTimeout</CODE></B> - The amount of time (in seconds) that
84 * a query will block before throwing an exception. If <= 0 then the
85 * query will not block. Optional, if not specified, then the value
86 * defaults to 0. This is not implemented by all logical databases.
87 * <LI> <B><CODE>TransactionTimeout</CODE></B> - The amount of time (in seconds)
88 * that a transaction will block before throwing an exception. If
89 * <= 0 then the transaction will not block. Optional, if not specified,
90 * then the value defaults to 0. This is not implemented by all
91 * logical databases.
92 * <LI> <B><CODE>MaxPreparedStatements</CODE></B> - If specified, overrides
93 * the JDBC <CODE>Connection.getMetaData().getMaxStatements()</CODE>
94 * value. If less than zero, use the meta data value. Optional,
95 * default is to use the meta data.
96 * </UL>
97 * It would be nice to add a config parameter that would disable caching
98 * of PreparedStatements.
99 *
100 * @author Mark Diekhans
101 * @author Kyle Clark
102 * @author Paul Morgan
103 * @since LBS1.8
104 * @version $Revision: 1.10.12.1 $
105 */
106 public class StandardConnectionAllocator implements ConnectionAllocator {
107
108 /**
109 * Reference to the logical database for easy access to the
110 * connection pool.
111 */
112 protected LogicalDatabase logicalDatabase = null;
113
114 /**
115 * JDBC URL of database.
116 */
117 protected String url;
118
119 /**
120 * SQL user name.
121 */
122 protected String user;
123
124 /**
125 * SQL password..
126 */
127 protected String password;
128
129 /**
130 * Maximum number of connections in the pool.
131 * If this value is <= zero, then create as many
132 * connections as possible.
133 */
134 private int maxPoolSize;
135
136 /**
137 * Current size of the pool; includes allocated connections.
138 */
139 private int currentPoolSize;
140
141 /**
142 * Maximum size the pool ever got to, regardless of generation.
143 */
144 private int biggestPoolSize;
145
146 /**
147 * Date at which the biggest pool size occured.
148 */
149 private Date biggestPoolDate;
150
151 /**
152 * Number of queries or transactions on this logical database.
153 */
154 protected long numRequests;
155
156 /**
157 * The actual pool of DBConnection objects.
158 */
159 private Stack pool;
160
161 /**
162 * Indicates if logging is enabled.
163 */
164 protected boolean sqlLogging;
165
166 /**
167 * Maximum amount of time in milliseconds to wait for a connection.
168 */
169 private int timeOut;
170
171 /**
172 * Maximum amount of time in seconds to block on a query. The
173 * DBQuery object will retrieve this value from the connection.
174 */
175 protected int queryTimeOut;
176
177 /**
178 * Maximum amount of time in seconds to block on a transaction. The
179 * DBTransaction object will retrieve this value from the connection.
180 */
181 protected int transactionTimeOut;
182
183 /**
184 * Maximum number of prepared statements to use; if less-than zero,
185 * then JDBC is queried for this value.
186 */
187 protected int maxPreparedStatements;
188
189 /**
190 * Generation number. When an SQL error occurs, all objects of the
191 * same generation or earlier are dropped.
192 */
193 protected int generation = 1;
194
195 /**
196 * The log channel.
197 */
198 private LogChannel log = LBS.getLogChannel();
199
200
201 /**
202 * Create a new connection in the pool.
203 *
204 * @exception java.sql.SQLException If a SQL error occures.
205 */
206 protected DBConnection createConnection ()
207 throws java.sql.SQLException {
208 DBConnection dbConnection =
209 new StandardDBConnection (this, url, user, password,
210 maxPreparedStatements,
211 sqlLogging, generation);
212 return dbConnection;
213 }
214
215
216 /**
217 * Initialize the connection allocator object. Connections are
218 * opened on demand and stored in a pool.
219 *
220 * @param url JDBC URL of database.
221 * @param user SQL user name.
222 * @param password SQL password.
223 * @param maxPoolSize Maximum number of connections.
224 * @param sqlLogging Specifying <CODE>true</CODE> enables SQL logging.
225 * @param timeOut Set the maximum amount of time in milliseconds
226 * to wait for a new connection.
227 * @exception ConfigException if bad configuration information is
228 * given in the config file.
229 */
230 protected StandardConnectionAllocator (LogicalDatabase logicalDatabase,
231 Config conConfig)
232 throws ConfigException
233 {
234 this.logicalDatabase = logicalDatabase;
235
236 try {
237 url = conConfig.getString ("Url");
238 user = conConfig.getString ("User");
239 password = conConfig.getString ("Password");
240 timeOut = conConfig.getInt("AllocationTimeout", 1000);
241 maxPoolSize = conConfig.getInt ("MaxPoolSize", 0);
242 sqlLogging = conConfig.getBoolean ("Logging", false);
243 queryTimeOut = conConfig.getInt("QueryTimeout", 0);
244 transactionTimeOut = conConfig.getInt("TransactionTimeout", 0);
245 maxPreparedStatements = conConfig.getInt("MaxPreparedStatements", -1);
246 } catch (KeywordValueException except) {
247 throw new ConfigException ("Bad DatabaseManager.DB." + logicalDatabase.getName() + ".Connection section defined in config file.");
248 }
249
250 currentPoolSize = 0;
251 pool = new Stack ();
252
253 biggestPoolSize = 0;
254 biggestPoolDate = new Date();
255 numRequests = 0;
256 }
257
258
259 /**
260 * Allocate a connection to a thread. If none are available, grow the
261 * pool. If the pool is alredy its maximum size, then the thread waits.
262 *
263 * @return The allocated connection object.
264 * @exception SQLException
265 * If a SQL error occures.
266 */
267 public synchronized DBConnection allocate ()
268 throws SQLException
269 {
270 //
271 // It isn't always possible to determine the maximum
272 // number of connections allowed to the database because
273 // of JDBC driver differences. We assume connections are
274 // available until we fail to allocate one or we reach
275 // the maximum configured.
276 //
277 boolean createNewConn = true;
278 while (pool.empty()) {
279 if (createNewConn &&
280 ((currentPoolSize < maxPoolSize) || (maxPoolSize <= 0))) {
281 try {
282 pool.push(createConnection());
283 currentPoolSize++;
284 if (currentPoolSize > biggestPoolSize) {
285 biggestPoolSize = currentPoolSize;
286 biggestPoolDate = new Date();
287 }
288 } catch (SQLException e) {
289 if (currentPoolSize > 0) {
290 log.write(Logger.NOTICE,
291 "ConnectionAllocator: " +
292 "failed to allocate a new connection due to" +
293 e.toString() +
294 "Error code: " + e.getErrorCode() + "\n" +
295 "SQLState: " + e.getSQLState() + "\n" +
296 "\nCurrent pool size is: " + currentPoolSize +
297 "\nMaximum configured pool size is now " +
298 maxPoolSize + "\nContinuing...\n");
299 createNewConn = false;
300 } else {
301 log.write(Logger.ALERT,
302 "ConnectionAllocator: " +
303 "failed to allocate a new connection" +
304 "\nThe connection pool is empty!\n");
305 throw e;
306 }
307 }
308 } else {
309 try {
310 if (timeOut > 0) {
311 wait(timeOut);
312 if (pool.empty()) {
313 log.write(Logger.ALERT,
314 "ConnectionAllocator: " +
315 "allocation of a new connection timed out." +
316 "Possible dead lock avoided.");
317 String msg =
318 "Connections are currently unavailable.\n" +
319 "Possible dead lock avoided.";
320 throw new SQLException(msg);
321 }
322 }
323 else {
324 wait();
325 }
326 }
327 catch (InterruptedException intEx) {}
328 }
329 }
330
331 //
332 // A connection is available.
333 //
334 DBConnection conn = (DBConnection)pool.pop();
335 conn.allocate();
336 return conn;
337 }
338
339
340 /**
341 * Return a connection to the pool. If it is of an old generation,
342 * close and drop.
343 *
344 * @param dbConnection The connection object to return.
345 */
346 public synchronized void release (DBConnection dbConnection) {
347
348 if (dbConnection.getGeneration () < generation) {
349 dbConnection.close ();
350 currentPoolSize--;
351 } else {
352 pool.push (dbConnection);
353 }
354 notify ();
355 }
356
357
358 /**
359 * Called when a connection in this pool has an SQL error.
360 * All unallocated connections in the pool are dropped if
361 * they have a generation number less
362 * than or equal to the error connection.
363 * If the current generation number
364 * is the same as this generation number, increment it.
365 * This way so that all outstanding connections
366 * (including the one passed in) of the same generation
367 * are dropped when returned.
368 *
369 * @param dbConnection The connection object to drop.
370 * The connection should be
371 * returned to the pool after this function returns.
372 */
373 public synchronized void drop (DBConnection dbConnection) {
374
375 if (generation <= dbConnection.getGeneration ()) {
376 generation++; // new generation
377 }
378
379 // Delete all entries of the last generation.
380 Stack holdPool = new Stack ();
381 while (!pool.empty ()) {
382 DBConnection connect = (DBConnection) pool.pop ();
383 if (connect.getGeneration () < generation) {
384 connect.close ();
385 currentPoolSize--;
386 } else {
387 holdPool.push (connect);
388 }
389 }
390
391 // Put all current generation entries back in the pool.
392 while (!holdPool.empty ()) {
393 pool.push (holdPool.pop ());
394 }
395 notify ();
396 }
397
398
399 /**
400 * Called when the database manager is shutting down:
401 * Close all connections immediately.
402 */
403 public synchronized void dropAllNow () {
404
405 while (!pool.empty ()) {
406 DBConnection connect = (DBConnection) pool.pop ();
407 connect.close ();
408 currentPoolSize--;
409 }
410 }
411
412
413 /**
414 * Return the number of currently active connections.
415 *
416 * @return The number of connections.
417 */
418 public int getActiveCount () {
419 return currentPoolSize;
420 }
421
422
423 /**
424 * Return the maximum number of connections active at one time.
425 *
426 * @return The number of connections.
427 */
428 public int getMaxCount () {
429 return biggestPoolSize;
430 }
431
432
433 /**
434 * Return the time when the maximum connection count occured.
435 *
436 * @return The <CODE>Date</CODE> when the maximum connection
437 * count occured.
438 */
439 public Date getMaxCountDate () {
440 return biggestPoolDate;
441 }
442
443
444 /**
445 * Reset the maximum connection count and date.
446 */
447 public void resetMaxCount () {
448 biggestPoolSize = currentPoolSize;
449 biggestPoolDate = new Date();
450 }
451
452
453 /**
454 * Return the number of database requests.
455 *
456 * @return The number of database requests (queries or transactions).
457 */
458 public long getRequestCount () {
459 return numRequests;
460 }
461
462
463 /**
464 * Finalizer.
465 * If any connections allocated by this object have not been closed,
466 * this method ensures that garbage collection does so.
467 */
468 protected void finalize() {
469 dropAllNow();
470 }
471 }