001: /*
002: DBPool - JDBC Connection Pool Manager
003: Copyright (c) Giles Winstanley
004: */
005: package snaq.db;
006:
007: import snaq.util.*;
008: import java.sql.*;
009: import java.util.*;
010:
011: /**
012: * Implementation of a database connection pool.
013: * @see snaq.db.CacheConnection
014: * @see snaq.db.CachedCallableStatement
015: * @see snaq.db.CachedPreparedStatement
016: * @author Giles Winstanley
017: */
018: public class ConnectionPool extends ObjectPool implements Comparable {
019: private String url, user, pass;
020: private Properties props;
021: private ConnectionValidator validator = new DefaultValidator();
022: private PasswordDecoder decoder;
023: private boolean cacheSS, cachePS, cacheCS;
024: private List listeners = new ArrayList();
025:
026: /**
027: * Creates new connection pool.
028: * @param name pool name
029: * @param poolSize maximum number of pooled objects, or 0 for no limit
030: * @param maxSize maximum number of possible objects, or 0 for no limit
031: * @param expiryTime expiry time (milliseconds) for pooled object, or 0 for no expiry
032: * @param url JDBC connection URL
033: * @param username database username
034: * @param password password for the database username supplied
035: */
036: public ConnectionPool(String name, int poolSize, int maxSize,
037: long expiryTime, String url, String username,
038: String password) {
039: super (name, poolSize, maxSize, expiryTime);
040: this .url = url;
041: this .user = username;
042: this .pass = password;
043: this .props = null;
044: setCaching(true);
045: addObjectPoolListener(new EventRelay());
046: }
047:
048: /**
049: * Creates new connection pool.
050: * @param name pool name
051: * @param poolSize maximum number of pooled objects, or 0 for no limit
052: * @param maxSize maximum number of possible objects, or 0 for no limit
053: * @param expiryTime expiry time (milliseconds) for pooled object, or 0 for no expiry
054: * @param url JDBC connection URL
055: * @param props connection properties
056: */
057: public ConnectionPool(String name, int poolSize, int maxSize,
058: long expiryTime, String url, Properties props) {
059: this (name, poolSize, maxSize, expiryTime, url, null, null);
060: this .props = props;
061: this .pass = props.getProperty("password");
062: addObjectPoolListener(new EventRelay());
063: }
064:
065: /**
066: * Creates a new database connection.
067: */
068: protected Reusable create() throws SQLException {
069: Connection con = null;
070: CacheConnection ccon = null;
071: try {
072: if (props != null) {
073: if (decoder != null)
074: props.setProperty("password", new String(decoder
075: .decode(pass)));
076: log("Getting connection (properties): " + url);
077: con = DriverManager.getConnection(url, props);
078: } else if (user != null) {
079: try {
080: if (decoder != null) {
081: log("Getting connection (user/enc.pass): "
082: + url);
083: con = DriverManager.getConnection(url, user,
084: new String(decoder.decode(pass)));
085: } else {
086: log("Getting connection (user/pass): " + url);
087: con = DriverManager.getConnection(url, user,
088: pass);
089: }
090: } catch (SQLException sqle) {
091: log("Failed to connect with standard authentication...trying with just JDBC URL");
092: log("Getting connection (URL only): " + url);
093: con = DriverManager.getConnection(url);
094: }
095: } else
096: con = DriverManager.getConnection(url);
097:
098: // Add caching wrapper to connection
099: ccon = new CacheConnection(this , con);
100: ccon.setCacheStatements(cacheSS);
101: ccon.setCachePreparedStatements(cachePS);
102: ccon.setCacheCallableStatements(cacheCS);
103: log("Created a new connection");
104:
105: // Check for warnings
106: SQLWarning warn = con.getWarnings();
107: while (warn != null) {
108: log("Warning - " + warn.getMessage());
109: warn = warn.getNextWarning();
110: }
111: } catch (SQLException sqle) {
112: log(sqle, "Can't create a new connection for " + url);
113: // Clean up open connection.
114: try {
115: con.close();
116: } catch (SQLException sqle2) {
117: }
118: // Rethrow exception.
119: throw sqle;
120: }
121: return ccon;
122: }
123:
124: /**
125: * Validates a connection.
126: */
127: protected boolean isValid(final Reusable o) {
128: if (o == null)
129: return false;
130: if (validator == null)
131: return true;
132:
133: try {
134: boolean valid = validator.isValid((Connection) o);
135: if (!valid)
136: fireValidationErrorEvent();
137: return valid;
138: } catch (Exception e) {
139: log(e, "Exception during validation");
140: return false;
141: }
142: }
143:
144: /**
145: * Sets the validator class for connections.
146: */
147: public void setValidator(ConnectionValidator cv) {
148: validator = cv;
149: }
150:
151: /**
152: * Returns the current validator class.
153: */
154: public ConnectionValidator getValidator() {
155: return validator;
156: }
157:
158: /**
159: * Sets the password decoder class.
160: */
161: public void setPasswordDecoder(PasswordDecoder pd) {
162: decoder = pd;
163: }
164:
165: /**
166: * Returns the current password decoder class.
167: */
168: public PasswordDecoder getPasswordDecoder() {
169: return decoder;
170: }
171:
172: /**
173: * Closes the given connection.
174: */
175: protected void destroy(final Reusable o) {
176: if (o == null)
177: return;
178: try {
179: ((CacheConnection) o).release();
180: log("Destroyed connection");
181: } catch (SQLException e) {
182: log(e, "Can't destroy connection");
183: }
184: }
185:
186: /**
187: * Gets a connection from the pool.
188: */
189: public Connection getConnection() throws SQLException {
190: try {
191: Reusable o = super .checkOut();
192: if (o != null) {
193: CacheConnection cc = (CacheConnection) o;
194: cc.setOpen();
195: return cc;
196: }
197: return null;
198: } catch (Exception e) {
199: log(e, "Error getting connection");
200: if (e instanceof SQLException)
201: throw (SQLException) e;
202: else {
203: Throwable t = e.getCause();
204: while (t != null) {
205: log(e, "Error getting connection");
206: t = t.getCause();
207: }
208: throw new SQLException(e.getMessage());
209: }
210: }
211: }
212:
213: /**
214: * Gets a connection from the pool.
215: */
216: public Connection getConnection(long timeout) throws SQLException {
217: try {
218: Object o = super .checkOut(timeout);
219: if (o != null) {
220: CacheConnection cc = (CacheConnection) o;
221: cc.setOpen();
222: return cc;
223: }
224: return null;
225: } catch (Exception e) {
226: if (e instanceof SQLException)
227: throw (SQLException) e;
228: else {
229: log(e, "Error getting connection");
230: throw new SQLException(e.getMessage());
231: }
232: }
233: }
234:
235: /**
236: * Returns a connection to the pool (for internal use only).
237: * Connections obtained from the pool should be returned by calling the
238: * close() method on the connection.
239: */
240: protected void freeConnection(Connection c) throws SQLException {
241: if (c == null || !CacheConnection.class.isInstance(c))
242: log("Attempt to return invalid item");
243: else {
244: CacheConnection cc = (CacheConnection) c;
245: super .checkIn((Reusable) c);
246: }
247: }
248:
249: /**
250: * Determines whether to perform statement caching.
251: * This applies to all types of statements (normal, prepared, callable).
252: */
253: public void setCaching(boolean b) {
254: cacheSS = cachePS = cacheCS = b;
255: }
256:
257: /**
258: * Determines whether to perform statement caching.
259: * @param ss whether to cache Statement objects
260: * @param ps whether to cache PreparedStatement objects
261: * @param cs whether to cache CallableStatement objects
262: */
263: public void setCaching(boolean ss, boolean ps, boolean cs) {
264: cacheSS = ss;
265: cachePS = ps;
266: cacheCS = cs;
267: }
268:
269: /** Returns a descriptive string for this pool instance. */
270: public String toString() {
271: return getName();
272: }
273:
274: /** Compares this instances to other instances by name. */
275: public int compareTo(Object o) {
276: return this .toString().compareTo(
277: ((ConnectionPool) o).toString());
278: }
279:
280: //**************************
281: // Event-handling methods
282: //**************************
283:
284: /**
285: * Adds an ConnectionPoolListener to the event notification list.
286: */
287: public final void addConnectionPoolListener(ConnectionPoolListener x) {
288: listeners.add(x);
289: }
290:
291: /**
292: * Removes an ConnectionPoolListener from the event notification list.
293: */
294: public final void removeConnectionPoolListener(
295: ConnectionPoolListener x) {
296: listeners.remove(x);
297: }
298:
299: private final void fireValidationErrorEvent() {
300: if (listeners.isEmpty())
301: return;
302: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
303: ConnectionPoolEvent.VALIDATION_ERROR);
304: for (Iterator iter = listeners.iterator(); iter.hasNext();)
305: ((ConnectionPoolListener) iter.next())
306: .validationError(poolEvent);
307: }
308:
309: private final void firePoolCheckOutEvent() {
310: if (listeners.isEmpty())
311: return;
312: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
313: ConnectionPoolEvent.CHECKOUT);
314: for (Iterator iter = listeners.iterator(); iter.hasNext();)
315: ((ConnectionPoolListener) iter.next())
316: .poolCheckOut(poolEvent);
317: }
318:
319: private final void firePoolCheckInEvent() {
320: if (listeners.isEmpty())
321: return;
322: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
323: ConnectionPoolEvent.CHECKIN);
324: for (Iterator iter = listeners.iterator(); iter.hasNext();)
325: ((ConnectionPoolListener) iter.next())
326: .poolCheckIn(poolEvent);
327: }
328:
329: private final void fireMaxPoolLimitReachedEvent() {
330: if (listeners.isEmpty())
331: return;
332: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
333: ConnectionPoolEvent.MAX_POOL_LIMIT_REACHED);
334: for (Iterator iter = listeners.iterator(); iter.hasNext();)
335: ((ConnectionPoolListener) iter.next())
336: .maxPoolLimitReached(poolEvent);
337: }
338:
339: private final void fireMaxPoolLimitExceededEvent() {
340: if (listeners.isEmpty())
341: return;
342: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
343: ConnectionPoolEvent.MAX_POOL_LIMIT_EXCEEDED);
344: for (Iterator iter = listeners.iterator(); iter.hasNext();)
345: ((ConnectionPoolListener) iter.next())
346: .maxPoolLimitExceeded(poolEvent);
347: }
348:
349: private final void fireMaxSizeLimitReachedEvent() {
350: if (listeners.isEmpty())
351: return;
352: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
353: ConnectionPoolEvent.MAX_SIZE_LIMIT_REACHED);
354: for (Iterator iter = listeners.iterator(); iter.hasNext();)
355: ((ConnectionPoolListener) iter.next())
356: .maxSizeLimitReached(poolEvent);
357: }
358:
359: private final void fireMaxSizeLimitErrorEvent() {
360: if (listeners.isEmpty())
361: return;
362: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
363: ConnectionPoolEvent.MAX_SIZE_LIMIT_ERROR);
364: for (Iterator iter = listeners.iterator(); iter.hasNext();)
365: ((ConnectionPoolListener) iter.next())
366: .maxSizeLimitError(poolEvent);
367: }
368:
369: private final void fireParametersChangedEvent() {
370: if (listeners == null || listeners.isEmpty())
371: return;
372: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
373: ConnectionPoolEvent.PARAMETERS_CHANGED);
374: for (Iterator iter = listeners.iterator(); iter.hasNext();)
375: ((ConnectionPoolListener) iter.next())
376: .poolParametersChanged(poolEvent);
377: }
378:
379: private final void firePoolReleasedEvent() {
380: if (listeners.isEmpty())
381: return;
382: ConnectionPoolEvent poolEvent = new ConnectionPoolEvent(this ,
383: ConnectionPoolEvent.POOL_RELEASED);
384: for (Iterator iter = listeners.iterator(); iter.hasNext();)
385: ((ConnectionPoolListener) iter.next())
386: .poolReleased(poolEvent);
387: }
388:
389: /**
390: * Class to relay ObjectPoolEvents as ConnectionPoolEvents for convenience.
391: */
392: private final class EventRelay extends ObjectPoolEventAdapter {
393: public void poolCheckOut(ObjectPoolEvent evt) {
394: firePoolCheckOutEvent();
395: }
396:
397: public void poolCheckIn(ObjectPoolEvent evt) {
398: firePoolCheckInEvent();
399: }
400:
401: public void maxPoolLimitReached(ObjectPoolEvent evt) {
402: fireMaxPoolLimitReachedEvent();
403: }
404:
405: public void maxPoolLimitExceeded(ObjectPoolEvent evt) {
406: fireMaxPoolLimitExceededEvent();
407: }
408:
409: public void maxSizeLimitReached(ObjectPoolEvent evt) {
410: fireMaxSizeLimitReachedEvent();
411: }
412:
413: public void maxSizeLimitError(ObjectPoolEvent evt) {
414: fireMaxSizeLimitErrorEvent();
415: }
416:
417: public void poolParametersChanged(ObjectPoolEvent evt) {
418: fireParametersChangedEvent();
419: }
420:
421: public void poolReleased(ObjectPoolEvent evt) {
422: firePoolReleasedEvent();
423: listeners.clear();
424: }
425: }
426:
427: /**
428: * Default implementation of ConnectionValidator.
429: * This class simply checks a Connection with the <tt>isClosed()</tt> method.
430: */
431: static class DefaultValidator implements ConnectionValidator {
432: /**
433: * Validates a connection.
434: */
435: public boolean isValid(Connection con) {
436: try {
437: return !con.isClosed();
438: } catch (SQLException e) {
439: return false;
440: }
441: }
442: }
443: }
|